├── md5sums.txt ├── sha256sums.txt ├── Makefile ├── ansible └── apache2buddy.yml ├── a2bchk.sh ├── landing_page.pl ├── man └── apache2buddy_manpage.txt ├── README.md ├── LICENSE ├── Jenkinsfile ├── changelog └── apache2buddy.pl /md5sums.txt: -------------------------------------------------------------------------------- 1 | 1e5bdd25991b213e5999c2adc00514bd apache2buddy.pl 2 | -------------------------------------------------------------------------------- /sha256sums.txt: -------------------------------------------------------------------------------- 1 | 0fc4d15c2d78d380797cac6faf803e5ad08857cdec234db3d8d7be375dac973b apache2buddy.pl 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lazysums lazycheck lazytest lazyadd lazypush 2 | 3 | lazycheck: 4 | md5sum apache2buddy.pl 5 | cat md5sums.txt 6 | @echo 7 | sha256sum apache2buddy.pl 8 | cat sha256sums.txt 9 | 10 | lazysums: md5sums.txt sha256sums.txt 11 | 12 | md5sums.txt: apache2buddy.pl 13 | md5sum apache2buddy.pl >md5sums.txt 14 | 15 | sha256sums.txt: apache2buddy.pl 16 | sha256sum apache2buddy.pl >sha256sums.txt 17 | 18 | lazytest: 19 | sudo perl apache2buddy.pl 20 | 21 | lazyadd: 22 | git add apache2buddy.pl md5sums.txt sha256sums.txt 23 | 24 | lazypush: 25 | git push origin staging 26 | -------------------------------------------------------------------------------- /ansible/apache2buddy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Run apache2buddy on webservers 3 | hosts: webservers 4 | become: TRUE 5 | tasks: 6 | - name: Fetch apache2buddy script 7 | ansible.builtin.uri: 8 | url: https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl 9 | dest: /tmp/apache2buddy.pl 10 | 11 | - name: Run apache2buddy script 12 | command: perl /tmp/apache2buddy.pl --nocolor 13 | register: out 14 | - debug: var=out.stdout_lines 15 | 16 | - name: Cleanup downloaded content 17 | ansible.builtin.file: 18 | path: /tmp/apache2buddy.pl 19 | state: absent 20 | 21 | -------------------------------------------------------------------------------- /a2bchk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # example of testing md5sums prior to execution 3 | 4 | scriptmd5sum=`curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | md5sum | cut -d " " -f1` 5 | originmd5sum=`curl -s https://raw.githubusercontent.com/richardforth/apache2buddy/master/md5sums.txt | cut -d " " -f1` 6 | echo $scriptmd5sum 7 | echo $originmd5sum 8 | if [ $scriptmd5sum == $originmd5sum ] 9 | then 10 | scriptsha256sum=`curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | sha256sum | cut -d " " -f1` 11 | originsha256sum=`curl -s https://raw.githubusercontent.com/richardforth/apache2buddy/master/sha256sums.txt | cut -d " " -f1` 12 | echo $scriptsha256sum 13 | echo $originsha256sum 14 | if [ $scriptsha256sum == $originsha256sum ] 15 | then 16 | # execute the code, its safe - we can assume 17 | curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl 18 | if [[ $? != 0 ]]; then 19 | exit 1 20 | fi 21 | else 22 | echo "Error: SHA256SUM mismatch, execution aborted." 23 | exit 1 24 | fi 25 | else 26 | echo "Error: MD5SUM mismatch, execution aborted." 27 | exit 1 28 | fi 29 | -------------------------------------------------------------------------------- /landing_page.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $message = <<'END_MESSAGE'; 4 | 5 | ############# IMPORTANT SAFETY ANNOUNCEMENT ################# 6 | 7 | This is the NEW landing page for apache2buddy.pl 8 | 9 | Please don't curl and perl the domain any more. 10 | 11 | For security reasons, the following 12 | execution method will bring you to this page: 13 | 14 | # curl -sL apache2buddy.pl | perl 15 | 16 | Instead please run / bookmark the following: 17 | 18 | # curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl 19 | 20 | This method is much safer. 21 | 22 | For more information on this change refer to the README.md: 23 | https://github.com/richardforth/apache2buddy/blob/master/README.md 24 | 25 | Pay specific attention to the "Security Concerns" and 26 | "Typo squatting / camping is a thing and why you should be concerned" 27 | sections. 28 | 29 | If you still don't understand the dangers of typo camping / typo squatting, 30 | remember, you just ran THIS script, on your server as root. Thankfully I am 31 | a good guy. 32 | 33 | The domain will slowly be phased out and will eventually be released. 34 | This landing page marks the start of that process. 35 | 36 | I expect the domain will be up (and this page) and serving for at least 2 37 | years to catch all end-users to give ample chance to update their 38 | notes and stuff before the domain finally goes away. 39 | 40 | ############### END IMPORTANT SAFTEY ANNOUNCEMENT ############## 41 | 42 | END_MESSAGE 43 | 44 | print $message; 45 | print "Done.\n"; 46 | -------------------------------------------------------------------------------- /man/apache2buddy_manpage.txt: -------------------------------------------------------------------------------- 1 | NAME 2 | 3 | apache2buddy.pl (perl script) 4 | 5 | A Free and Open Source Project for apache webserver memory usage analysis. 6 | 7 | 8 | 9 | SYNOPSIS 10 | 11 | curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl [-] [options] 12 | 13 | 14 | DESCRIPTION 15 | 16 | A Free and Open Source Project for apache webserver memory usage analysis. 17 | 18 | It is hosted on the domain of the same name. 19 | 20 | 21 | 22 | OPTIONS 23 | 24 | If no options are specified, the basic tests will be run. 25 | 26 | -h, --help Print this help message 27 | -p, --port=PORT Specify an alternate port to check (default: 80) 28 | --pid=PID Specify a PID to bypass the "Multiple PIDS listening on port 80" error. 29 | -v, --verbose Use verbose output (this is very noisy, only useful for debugging) 30 | -n, --nocolor Use default terminal color, dont try to be all fancy! 31 | -H, --noheader Do not show header title bar. 32 | -N, --noinfo Do not show informational messages. 33 | -K, --no-ok Do not show OK messages. 34 | -W, --nowarn Do not show warning messages. 35 | -L, --light-term Show colours for a light background terminal. 36 | -r, --report Implies -HNWK or --noinfo --nowarn --no-ok --noheader --skip-maxclients --skip-php-fatal --skip-updates 37 | -P, --no-check-pid DON'T Check the Parent Pid File Size (only use if desperate for more info, results may be skewed). 38 | --skip-maxclients Skip checking in maxclients was hit recently, can be slow, especialy if you have large log files. 39 | --skip-php-fatal Skip checking for PHP FATAL errors, can be slow, especialy if you have large log files. 40 | --skip-updates Skip checking for package updates, can be slow or problematic, causing the script to hang. 41 | -O, --skip-os-version-check Skips past the OS version check. 42 | Allows one to bypass EOL version showstopper but be mindful: 43 | skipping the os version check is not recommended as features may be 44 | deprecated or removed and apache2buddy is not backward compatible 45 | with end of life operating systems, this may cause errors and unpredictable 46 | behaviour. 47 | 48 | 49 | 50 | OUTPUT KEY: 51 | 52 | [ -- ] = Information 53 | [ @@ ] = Advisory 54 | [ >> ] = Warning 55 | [ !! ] = Critical 56 | 57 | 58 | EXAMPLES 59 | 60 | curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl 61 | curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl - --help 62 | curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl - --report 63 | 64 | Note: The reason for the extra dash after perl and before options, is to tell perl we are sending the options to the script, not perl 65 | itself, otherwise perl displays its own help or complains about a missing option --report, for example. 66 | 67 | 68 | ENVIRONMENT 69 | 70 | LC_* variables are checked during preflight checks, they MUST be en_GB or en_US in order to avoid runtime errors. 71 | Script runs as root and requires apropriate PATH set up to access *sbin* commands. 72 | 73 | 74 | 75 | BUGS 76 | 77 | No known bugs. Bugs or issues can be found here: 78 | https://github.com/richardforth/apache2buddy/issues 79 | 80 | 81 | ISSUES 82 | 83 | There is an issue with some parent PIDs consuming large amounts of RAM, child pids inherit this 84 | but don't necessarily use it, which skews results. The current workaround is to check for PPID 85 | which is less than 50MB, if over 50MB, the script aborts. 86 | 87 | Other issues can be found here: 88 | https://github.com/richardforth/apache2buddy/issues 89 | 90 | AUTHOR 91 | 92 | Richard A. Forth 93 | richard DOT forth AT gmail DOT com 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Status 2 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/richardforth/apache2buddy/graphs/commit-activity) [![GitHub latest commit](https://badgen.net/github/last-commit/richardforth/apache2buddy)](https://GitHub.com/richardforth/apache2buddy/commit/) [![GitHub stars](https://badgen.net/github/stars/richardforth/apache2buddy)](https://GitHub.com/richardforth/apache2buddy/stargazers/) [![Generic badge](https://img.shields.io/badge/Tests-Passing-green.svg)](https://shields.io/) 3 | 4 | # OS Support Staus 5 | 6 | ## RedHat Family 7 | 8 | Red Hat is Licenced so I am unable to test it, but it is technically supported - testing is done via OracleLinux, Rocky and Alma. 9 | Note that CentOS is now removed from support and no longer tested against, as it is deprecated, but you can try with -O to skip OS os checks. 10 | 11 | [![Generic badge](https://img.shields.io/badge/RHEL-Unable%20To%20Test-red.svg)](https://shields.io/) 12 | 13 | [![Generic badge](https://img.shields.io/badge/Centos-Removed-red.svg)](https://shields.io/) 14 | 15 | [![Generic badge](https://img.shields.io/badge/Oracle%20Linux%208-Passing-green.svg)](https://shields.io/) 16 | [![Generic badge](https://img.shields.io/badge/Oracle%20Linux%209-Passing-green.svg)](https://shields.io/) 17 | 18 | [![Generic badge](https://img.shields.io/badge/Rocky%20Linux%208-Passing-green.svg)](https://shields.io/) 19 | [![Generic badge](https://img.shields.io/badge/Rocky%20Linux%209-Passing-green.svg)](https://shields.io/) 20 | 21 | [![Generic badge](https://img.shields.io/badge/AlmaLinux%208-Passing-green.svg)](https://shields.io/) 22 | [![Generic badge](https://img.shields.io/badge/AlmaLinux%209-Passing-green.svg)](https://shields.io/) 23 | 24 | [![Generic badge](https://img.shields.io/badge/AmazonLinux%202-Passing-green.svg)](https://shields.io/) 25 | [![Generic badge](https://img.shields.io/badge/AmazonLinux%202023-Passing-green.svg)](https://shields.io/) 26 | 27 | ## Ubuntu 28 | 29 | [![Generic badge](https://img.shields.io/badge/Ubuntu%2018.04-Passing-green.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/Ubuntu%2020.04-Passing-green.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/Ubuntu%2022.04-Passing-green.svg)](https://shields.io/) 30 | 31 | ## Debian 32 | 33 | [![Generic badge](https://img.shields.io/badge/Debian%2012-Passing-green.svg)](https://shields.io/) 34 | 35 | ## Bitnami (apache) 36 | 37 | [![Generic badge](https://img.shields.io/badge/Bitnami%20apache-Passing-green.svg)](https://shields.io/) 38 | 39 | ## Gentoo 40 | 41 | [![Generic badge](https://img.shields.io/badge/Gentoo-Unable%20To%20Test-red.svg)](https://shields.io/) 42 | [![Generic badge](https://img.shields.io/badge/Gentoo-Works%20with%20dash%20O%20Option-yellow.svg)](https://shields.io/) 43 | [![Generic badge](https://img.shields.io/badge/Gentoo-Passing%20in%20the%20wild-green.svg)](https://shields.io/) 44 | 45 | 46 | The rule of thumb is if its not listed, its not supported. 47 | Anything that says Unable To Test, means it should work, but can't be dockerized and isnt included in the Jenkinsfile for licensing reasons, and needs field testing. Badges for those items will be updated to "Passing in the wild" if seen working in the wild. 48 | 49 | # execution 50 | 51 | # curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl 52 | 53 | 54 | # Best Practice 55 | 56 | Best Practice is to check the code against either the md5sums or sha256sums (or both) before execution of the code. 57 | 58 | Example: 59 | 60 | ```bash 61 | #!/bin/bash 62 | # example of testing md5sums prior to execution 63 | 64 | scriptmd5sum=`curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | md5sum | cut -d " " -f1` 65 | originmd5sum=`curl -s https://raw.githubusercontent.com/richardforth/apache2buddy/master/md5sums.txt | cut -d " " -f1` 66 | echo $scriptmd5sum 67 | echo $originmd5sum 68 | if [ $scriptmd5sum == $originmd5sum ] 69 | then 70 | scriptsha256sum=`curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | sha256sum | cut -d " " -f1` 71 | originsha256sum=`curl -s https://raw.githubusercontent.com/richardforth/apache2buddy/master/sha256sums.txt | cut -d " " -f1` 72 | echo $scriptsha256sum 73 | echo $originsha256sum 74 | if [ $scriptsha256sum == $originsha256sum ] 75 | then 76 | # execute the code, its safe - we can assume 77 | curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/master/apache2buddy.pl | perl 78 | if [[ $? != 0 ]]; then 79 | exit 1 80 | fi 81 | else 82 | echo "Error: SHA256SUM mismatch, execution aborted." 83 | exit 1 84 | fi 85 | else 86 | echo "Error: MD5SUM mismatch, execution aborted." 87 | exit 1 88 | fi 89 | ``` 90 | 91 | If the md5sums or sha256sums do not match, then changes have been made and its untested, so do not proceed until they match. 92 | 93 | # Risk Factors 94 | 95 | - Running arbitrary code as root (Dangerous) 96 | - Compromised script could result in root level compromise of your server 97 | - Runaway processes doing not what they are supposed to (this actually happened in testing, thankfully all of the known exceptions have been caught) 98 | 99 | 100 | # Logging 101 | 102 | On every execution, an entry is made in a log file: /var/log/apache2buddy.log on your server. 103 | 104 | Example log line: 105 | 106 | 2016/05/24 10:14:15 Model: "Prefork" Memory: "490 MB" Maxclients: "50" Recommended: "54" Smallest: "8.49 MB" Avg: "8.49 MB" Largest: "8.49 MB" Highest Pct Remaining RAM: "91.84%" (86.64% TOTAL RAM) 107 | 108 | 109 | This is to help you get an idea of changes over time to your apache tuning requirements. Maybe this will help you decide when you need more RAM, or when you need to start streamlining your code. Tracking when performace started degrading. 110 | 111 | Remember it only puts a new entry in the log file on each new execution. Its not designed to be run as a cron job or anything. 112 | 113 | # Log Rotation 114 | 115 | Log rotation should not be necessary because this script is NOT designed to be run as a cron job so it should never really fill your disks, if you ran this on your server a year or six months ago, maybe its just nice to see what the results were from back then? You get the idea. 116 | 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright {yyyy} {name of copyright owner} 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | 205 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent none 3 | // Pre-Seed tzdata for php install on the ubuntu docker containers 4 | environment { 5 | DEBIAN_FRONTEND = 'noninteractive' 6 | TZ = 'Europe/London' 7 | } 8 | // We skip the default checkout SCM as we are running the tests in docker containers. 9 | // We only want to keep the last 3 builds on the Jenkins Controller to save diskspace. 10 | options { 11 | skipDefaultCheckout true 12 | buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '3') 13 | } 14 | 15 | stages { 16 | stage('Mandatory 2min Sleep') { 17 | steps { 18 | echo 'Sleeping for 2 minutes...' 19 | sleep time: 2, unit: 'MINUTES' 20 | } 21 | } 22 | stage('Docker BitnamiApache Staging') { 23 | agent { 24 | docker { 25 | image 'forric/bitnamiapache:latest' 26 | args '-u root:root --cap-add SYS_PTRACE' 27 | reuseNode true 28 | } 29 | } 30 | steps { 31 | script { 32 | sh 'install_packages php' 33 | sh 'php -v' 34 | sh 'cat /etc/os-release' 35 | echo "Checking with netstat..." 36 | def output_netstat = sh(script: "/opt/bitnami/apache/bin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 37 | echo "Command output:\n${output_netstat}" 38 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 39 | error "Output validation failed" 40 | } 41 | echo "installing iproute2 which contains ss..." 42 | sh 'install_packages iproute2' 43 | echo "Checking with ss..." 44 | def output_ss = sh(script: "/opt/bitnami/apache/bin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 45 | echo "Command output:\n${output_ss}" 46 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 47 | error "Output validation failed" 48 | } 49 | } 50 | } 51 | } 52 | stage('Docker OracleLinux8 Staging') { 53 | agent { 54 | docker { 55 | image 'forric/oraclelinux8:latest' 56 | args '-u root:root --cap-add SYS_PTRACE' 57 | reuseNode true 58 | } 59 | } 60 | steps { 61 | script { 62 | sh 'yum -y install hostname' 63 | sh 'yum -y install php' 64 | sh 'php -v' 65 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 66 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 67 | sh 'apachectl configtest' 68 | sh 'cat /etc/os-release' 69 | echo "Oracle Linux defaults to ss" 70 | echo "Checking with ss..." 71 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 72 | echo "Command output:\n${output_ss}" 73 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 74 | error "Output validation failed" 75 | } 76 | echo "Removing iproute which contained ss command" 77 | sh 'yum -y remove iproute' 78 | echo "installing net-tools which includes netstat" 79 | sh 'yum -y install net-tools' 80 | echo "Testing with netstat..." 81 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 82 | echo "Command output:\n${output_netstat}" 83 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 84 | error "Output validation failed" 85 | } 86 | } 87 | } 88 | } 89 | stage('Docker OracleLinux9 Staging') { 90 | agent { 91 | docker { 92 | image 'forric/oraclelinux9:latest' 93 | args '-u root:root --cap-add SYS_PTRACE' 94 | reuseNode true 95 | } 96 | } 97 | steps { 98 | script { 99 | sh 'yum -y install hostname' 100 | sh 'yum -y install php' 101 | sh 'php -v' 102 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 103 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 104 | sh 'apachectl configtest' 105 | sh 'cat /etc/os-release' 106 | echo "Oracle Linux defaults to ss" 107 | echo "Checking with ss..." 108 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 109 | echo "Command output:\n${output_ss}" 110 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 111 | error "Output validation failed" 112 | } 113 | echo "Removing iproute which contained ss command" 114 | sh 'yum -y remove iproute' 115 | echo "installing net-tools which includes netstat" 116 | sh 'yum -y install net-tools' 117 | echo "Testing with netstat..." 118 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 119 | echo "Command output:\n${output_netstat}" 120 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 121 | error "Output validation failed" 122 | } 123 | } 124 | } 125 | } 126 | stage('Docker RockyLinux8 Staging') { 127 | agent { 128 | docker { 129 | image 'forric/rockylinux8:latest' 130 | args '-u root:root --cap-add SYS_PTRACE' 131 | reuseNode true 132 | } 133 | } 134 | steps { 135 | script { 136 | sh 'yum -y install php' 137 | sh 'php -v' 138 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 139 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 140 | sh 'apachectl configtest' 141 | sh 'cat /etc/os-release' 142 | echo "Checking with netstat..." 143 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 144 | echo "Command output:\n${output_netstat}" 145 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 146 | error "Output validation failed" 147 | } 148 | echo "Installing iproute which contains ss command" 149 | sh 'yum -y install iproute' 150 | echo "Checking with ss..." 151 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 152 | echo "Command output:\n${output_ss}" 153 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 154 | error "Output validation failed" 155 | } 156 | } 157 | } 158 | } 159 | stage('Docker RockyLinux9 Staging') { 160 | agent { 161 | docker { 162 | image 'forric/rockylinux9:latest' 163 | args '-u root:root --cap-add SYS_PTRACE' 164 | reuseNode true 165 | } 166 | } 167 | steps { 168 | script { 169 | sh 'yum -y install php' 170 | sh 'php -v' 171 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 172 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 173 | sh 'apachectl configtest' 174 | sh 'cat /etc/os-release' 175 | echo "Checking with netstat..." 176 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 177 | echo "Command output:\n${output_netstat}" 178 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 179 | error "Output validation failed" 180 | } 181 | echo "Installing iproute which contains ss command" 182 | sh 'yum -y install iproute' 183 | echo "Checking with ss..." 184 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 185 | echo "Command output:\n${output_ss}" 186 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 187 | error "Output validation failed" 188 | } 189 | } 190 | } 191 | } 192 | stage('Docker AlmaLinux8 Staging') { 193 | agent { 194 | docker { 195 | image 'forric/almalinux8:latest' 196 | args '-u root:root --cap-add SYS_PTRACE' 197 | reuseNode true 198 | } 199 | } 200 | steps { 201 | script { 202 | sh 'rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8' 203 | sh 'yum -y install php' 204 | sh 'php -v' 205 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 206 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 207 | sh 'apachectl configtest' 208 | sh 'cat /etc/os-release' 209 | echo "Checking with netstat..." 210 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 211 | echo "Command output:\n${output_netstat}" 212 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 213 | error "Output validation failed" 214 | } 215 | echo "Installing iproute which contains ss command" 216 | sh 'yum -y install iproute' 217 | echo "Checking with ss..." 218 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 219 | echo "Command output:\n${output_ss}" 220 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 221 | error "Output validation failed" 222 | } 223 | } 224 | } 225 | } 226 | stage('Docker AlmaLinux9 Staging') { 227 | agent { 228 | docker { 229 | image 'forric/almalinux9:latest' 230 | args '-u root:root --cap-add SYS_PTRACE' 231 | reuseNode true 232 | } 233 | } 234 | steps { 235 | script { 236 | sh 'rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-9' 237 | sh 'yum -y install php' 238 | sh 'php -v' 239 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 240 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 241 | sh 'apachectl configtest' 242 | sh 'cat /etc/os-release' 243 | echo "Checking with netstat..." 244 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 245 | echo "Command output:\n${output_netstat}" 246 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 247 | error "Output validation failed" 248 | } 249 | echo "Installing iproute which contains ss command" 250 | sh 'yum -y install iproute' 251 | echo "Checking with ss..." 252 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 253 | echo "Command output:\n${output_ss}" 254 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 255 | error "Output validation failed" 256 | } 257 | } 258 | } 259 | } 260 | stage('Docker AmazonLinux 2 Staging') { 261 | agent { 262 | docker { 263 | image 'forric/amazonlinux2:latest' 264 | args '-u root:root --cap-add SYS_PTRACE' 265 | reuseNode true 266 | } 267 | } 268 | steps { 269 | script { 270 | sh 'yum -y install php' 271 | sh 'php -v' 272 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 273 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 274 | sh 'apachectl configtest' 275 | sh 'cat /etc/os-release' 276 | echo "Checking with netstat..." 277 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 278 | echo "Command output:\n${output_netstat}" 279 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 280 | error "Output validation failed" 281 | } 282 | echo "Installing iproute which contains ss command" 283 | sh 'yum -y install iproute' 284 | echo "Checking with ss..." 285 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 286 | echo "Command output:\n${output_ss}" 287 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 288 | error "Output validation failed" 289 | } 290 | } 291 | } 292 | } 293 | stage('Docker AmazonLinux 2023 Staging') { 294 | agent { 295 | docker { 296 | image 'forric/amazonlinux2023:latest' 297 | args '-u root:root --cap-add SYS_PTRACE' 298 | reuseNode true 299 | } 300 | } 301 | steps { 302 | script { 303 | sh 'yum -y install php' 304 | sh 'php -v' 305 | sh 'sed -i \'s/^LoadModule mpm_event_module/#LoadModule mpm_event_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 306 | sh 'sed -i \'s/^#LoadModule mpm_prefork_module/LoadModule mpm_prefork_module/\' /etc/httpd/conf.modules.d/00-mpm.conf' 307 | sh 'apachectl configtest' 308 | sh 'cat /etc/os-release' 309 | echo "Checking with netstat..." 310 | def output_netstat = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 311 | echo "Command output:\n${output_netstat}" 312 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 313 | error "Output validation failed" 314 | } 315 | echo "Installing iproute which contains ss command" 316 | sh 'yum -y install iproute' 317 | echo "Checking with ss..." 318 | def output_ss = sh(script: "/usr/sbin/httpd -k start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 319 | echo "Command output:\n${output_ss}" 320 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 321 | error "Output validation failed" 322 | } 323 | } 324 | } 325 | } 326 | stage('Docker Ubuntu1804 Staging') { 327 | agent { 328 | docker { 329 | image 'forric/ubuntu1804:latest' 330 | args '-u root:root --cap-add SYS_PTRACE' 331 | reuseNode true 332 | } 333 | } 334 | steps { 335 | script { 336 | sh 'apt-get update' 337 | sh 'apt -y install php' 338 | sh 'php -v' 339 | sh 'cat /etc/os-release' 340 | echo "Checking with netstat..." 341 | def output_netstat = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 342 | echo "Command output:\n${output_netstat}" 343 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 344 | error "Output validation failed" 345 | } 346 | echo "Installing iproute2 which contains ss command" 347 | sh 'apt -y install iproute2' 348 | echo "Checking with ss..." 349 | def output_ss = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 350 | echo "Command output:\n${output_ss}" 351 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 352 | error "Output validation failed" 353 | } 354 | } 355 | } 356 | } 357 | stage('Docker Ubuntu2004 Staging') { 358 | agent { 359 | docker { 360 | image 'forric/ubuntu2004:latest' 361 | args '-u root:root --cap-add SYS_PTRACE' 362 | reuseNode true 363 | } 364 | } 365 | steps { 366 | script { 367 | sh 'apt-get update' 368 | sh 'apt -y install php' 369 | sh 'php -v' 370 | sh 'cat /etc/os-release' 371 | echo "Checking with netstat..." 372 | def output_netstat = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 373 | echo "Command output:\n${output_netstat}" 374 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 375 | error "Output validation failed" 376 | } 377 | echo "Installing iproute2 which contains ss command" 378 | sh 'apt -y install iproute2' 379 | echo "Checking with ss..." 380 | def output_ss = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 381 | echo "Command output:\n${output_ss}" 382 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 383 | error "Output validation failed" 384 | } 385 | } 386 | } 387 | } 388 | stage('Docker Ubuntu2204 Staging') { 389 | agent { 390 | docker { 391 | image 'forric/ubuntu2204:latest' 392 | args '-u root:root --cap-add SYS_PTRACE' 393 | reuseNode true 394 | } 395 | } 396 | steps { 397 | script { 398 | sh 'apt-get update' 399 | sh 'apt -y install php' 400 | sh 'php -v' 401 | sh 'cat /etc/os-release' 402 | echo "Checking with netstat..." 403 | def output_netstat = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 404 | echo "Command output:\n${output_netstat}" 405 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 406 | error "Output validation failed" 407 | } 408 | echo "Installing iproute2 which contains ss command" 409 | sh 'apt -y install iproute2' 410 | echo "Checking with ss..." 411 | def output_ss = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 412 | echo "Command output:\n${output_ss}" 413 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 414 | error "Output validation failed" 415 | } 416 | } 417 | } 418 | } 419 | stage('Docker Ubuntu2404 Staging') { 420 | agent { 421 | docker { 422 | image 'forric/ubuntu2404:latest' 423 | args '-u root:root --cap-add SYS_PTRACE' 424 | reuseNode true 425 | } 426 | } 427 | steps { 428 | script { 429 | sh 'apt-get update' 430 | sh 'apt -y install php' 431 | sh 'php -v' 432 | sh 'cat /etc/os-release' 433 | echo "Checking with netstat..." 434 | def output_netstat = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 435 | echo "Command output:\n${output_netstat}" 436 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 437 | error "Output validation failed" 438 | } 439 | echo "Installing iproute2 which contains ss command" 440 | sh 'apt -y install iproute2' 441 | echo "Checking with ss..." 442 | def output_ss = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 443 | echo "Command output:\n${output_ss}" 444 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 445 | error "Output validation failed" 446 | } 447 | } 448 | } 449 | } 450 | stage('Docker Debian 12 Staging') { 451 | agent { 452 | docker { 453 | image 'forric/debian12:latest' 454 | args '-u root:root --cap-add SYS_PTRACE' 455 | reuseNode true 456 | } 457 | } 458 | steps { 459 | script { 460 | sh 'apt-get update' 461 | sh 'apt -y install php' 462 | sh 'php -v' 463 | sh 'cat /etc/os-release' 464 | echo "Checking with netstat..." 465 | def output_netstat = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 466 | echo "Command output:\n${output_netstat}" 467 | if (!output_netstat.contains("[ OK ] Using 'netstat' for socket statistics.")) { 468 | error "Output validation failed" 469 | } 470 | echo "Installing iproute2 which contains ss command" 471 | sh 'apt -y install iproute2' 472 | echo "Checking with ss..." 473 | def output_ss = sh(script: "service apache2 start && curl -sL https://raw.githubusercontent.com/richardforth/apache2buddy/staging/apache2buddy.pl | perl - -n", returnStdout: true).trim() 474 | echo "Command output:\n${output_ss}" 475 | if (!output_ss.contains("[ OK ] Using 'ss' for socket statistics.")) { 476 | error "Output validation failed" 477 | } 478 | } 479 | } 480 | } 481 | } 482 | } -------------------------------------------------------------------------------- /changelog: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | 3 | When Who What 4 | ====================================================================================== 5 | 2022-10-30 Richard Forth Adding support for Ubuntu 22.04: 6 | - Created a Dockerfile 7 | - Pushed a new Docker Image 8 | - Added a Ubuntu 22.04 stage in Jenkinsfile 9 | - Updated main script to allo it to run on 10 | 22.04 11 | - Further testing is needed. 12 | - Issue #403 13 | 2022-10-30 Richard Forth Added a2bchk.sh script and extra step in 14 | Jenkins pipelene. Credit to Dakman for the 15 | reminder! :) Issue #402 16 | 2022-03-29 Richard Forth Adding support for AmazonLinux 17 | - Pending CICD testing 18 | - Fixed issue with AmazonLinux 2 docker 19 | container (not in this repo) 20 | 2022-03-29 Richard Forth Adding support for AmazonLinux 21 | - Tests Failing Currently 22 | - Work in progress 23 | 2022-03-28 Richard Forth Added support for Alma Linux 8 24 | - Tests Passing 25 | 2022-03-28 Richard Forth Added support for Rocky Linux 8 26 | - Tests Passing 27 | 2022-03-28 Richard Forth Removed support for EOL Operating Systems 28 | 2021-11-03 Emil Cazamir Added SuSE Linux v.12 compatibility 29 | Using httpd/apache2 running in prefork mode, tested 30 | with SLES 12 SP5 31 | 2021-10-28 w4zu Add support for Debian 11 32 | 2021-02-22 Richard Forth Added an important message about 33 | Domain expiry and non renewal, and the risks 34 | and mitigations that were previously put in 35 | place. 36 | - Closes Issue #367 37 | - Removed support for RHEL / CentOS 6, as it 38 | went EOL November 30th 2020. 39 | - Closes issue #364 40 | - Updated messaging with regards to worker/mpm 41 | report inconsistency. 42 | - Closes issue #363 43 | Added php to Dockerfiles 44 | - Closes #356 45 | 2020-10-03 Richard Forth Added an important message about 46 | troubleshooting apache vs running this script. 47 | 2020-09-30 Richard Forth Clarification onf "10% of MAX" wording to: 48 | "90-100% of remaining RAM". 49 | 2020-09-09 Richard Forth Address issue #355 50 | Made a change to how debian/ubuntu php.ini is 51 | located to make it more future proof. 52 | 2020-07-21 Richard Forth Address issue #351 53 | - Fixed applied to patch a break introduced in #347. 54 | Caused the entire config to be empty. 55 | In turn caused lots of CONFIG NOT FOUND, even 56 | when the configuration elements existed on the 57 | system. 58 | 2020-07-21 Richard Forth Address issue #350 59 | - Change wording about worker model. 60 | 2020-07-17 Richard Forth Address issue #348 61 | - Apache user not found in ubuntu installs. 62 | - Checks /etc/apache2/envvars if 63 | $apache_user_config is blank. 64 | 2020-07-17 Richard Forth Address issue #347 65 | - snag a chomp issue as a result of fix that 66 | went in. Also fixed date in changlog I forgot 67 | to change when copying a line, my bad. 68 | 2020-07-17 Richard Forth Address issue #347 69 | - sanity check included files when building 70 | config array. Requires testng and bugfixing if 71 | necessary. 72 | 2020-07-08 Sebastien Badia Detection support for php 7.3 73 | - Closes #344 74 | 2020-07-02 Richard Forth Addresses issue #342 75 | - fix grep out root on remi installs 76 | - Closes #342 77 | 2020-06-24 Richard Forth Attempt to address issue #300 78 | Report and compare running and configured 79 | apache user. 80 | 2020-06-19 Richard Forth Forced apache2buddy to respect the 81 | --no-check-pid option. 82 | Closes #309 83 | 2020-06-19 Richard Forth Added support for Ubuntu 20.04 84 | Closes #331 85 | 2020-06-19 Richard Forth Added a sanity check for issue #325 86 | Closes #325 87 | 2020-06-19 Richard Forth Added a fix up working towards apache 2.4.43 88 | - issue with apachectl, first spotted in 89 | Fedora 32 90 | - added fallback to httpd. 91 | 2020-06-19 Richard Forth Added a fix up working towards ubuntu 20.04 92 | support. 93 | - requires further testing 94 | - use '--skip-os-version-check' for now. 95 | - This may also fix some issues in Fedora 32. 96 | 2020-06-17 Richard Forth Added support for Debian 10. 97 | - Added 10 to list of supported debian 98 | versions. 99 | 2020-06-17 Richard Forth Added Dockerfile for Debian10 testing. 100 | 2020-06-16 Richard Forth Added Dockerfile for CentOS7 testing. 101 | 2020-05-31 "pes-soft" Fix error getting the list of pids introduced in #327 102 | 2020-05-31 "pes-soft" Add compatibility for Apache 2.4 from CentOS7 103 | Software Collections #327 104 | 2020-03-27 Richard Forth Closes #319. 105 | myip.dnsomatic.com Too Many Requests, error 106 | suppression. 107 | 108 | NOTES/ERRATA: 109 | Commit references #316, meant to be #319. 110 | 2020-03-14 "wiechu" Updates to the way OS Distribution is detected. 111 | 2020-03-12 "wiechu" Few cosmetic changes 112 | 2020-03-12 Richard Forth / Fixed Package management Issue - issue #312 113 | "wiechu" 114 | 2020-03-10 Mark Hyde PR to address backward compatibilty with 115 | MaxClients directive in apache 2.4 116 | 2019-12-28 Richard Forth Quick fix for issue #304 117 | Report MaxConnectionsPerChild as well as 118 | MaxRequestsPerChild. 119 | 2019-04-17 Richard Forth Ubuntu 14.04 went EOL. 120 | 2019-02-14 Richard Forth Closes #250 121 | Make the serverlimit apply only to prefork 122 | model. 123 | 2019-02-14 Richard Forth Closes #289 124 | Apply fixup for borked ps command on Plesk 125 | servers. 126 | 2019-02-12 Richard Forth Closes #294 127 | Add verbiage about new --skip-os-version-check 128 | option. 129 | 2019-02-12 psytester PR #294 130 | exclude rotatelogs binary from $current_proc_count 131 | calculation 132 | 2019-02-12 Richard Forth Closes Issue #292 133 | Updated man page textfile for 2019 134 | 2019-02-09 Richard Forth Closes Issue #283 135 | This update treates varnich differently to 136 | other detected services due to changes in 137 | behvious from 4.1+ 138 | 2019-02-09 Richard Forth Revert changes made in #283 139 | This went in staging but then got reverted as 140 | it caused problems and had inexpected 141 | behaviour. 142 | 2019-02-09 Richard Forth Closes Issue #283 143 | Service detection inprovements to include 144 | services not running as root (eg vcache) 145 | 2019-02-01 Richard Forth Closes Issue #12 146 | Detect and skip unexpected directories in 147 | glob. Previously these were added to the list 148 | of files for processing and caused an 149 | exception to be raised. 150 | 2019-01-23 Richard Forth Closes Issue #287 151 | Typo on --skip-os-version-check option 152 | rectified. 153 | 2019-01-08 psytester Added support for SUSE Linux and apache in 154 | /opt/apache2 155 | 2018-12-02 Richard Forth Closes Issue #281 156 | Php memory limit was only interpretted in MB. 157 | Forced a change that accepts whatever is in 158 | the config, eg '128M' or '16G'. 159 | 2018-10-13 Richard Forth Closes Issue #278 160 | logs to log (spelling correction in verbiage) 161 | 2018-10-13 Richard Forth Closes Issue #194 162 | Detect issues getting package updates. 163 | 2018-10-13 Richard Forth Closes Issue #245 164 | Skip OS Version Check option, see --help. 165 | 2018-10-13 Richard Forth Closes Issue #221 166 | Log Uptime in /var/log/apache2buddy.log 167 | 2018-10-13 Richard Forth Closes Issue #251 168 | Warn if vhost_count exceeds maxclients 169 | 2018-10-12 Richard Forth Closes Issue #273 Graceful catch of syntax 170 | error in httpd.conf when parsing config. 171 | 2018-10-12 Richard Forth Closes Issue #252 Add verbiage about Apache 172 | 2.2 EOL. Refer to https://httpd.apache.org/. 173 | 2018-10-12 Richard Forth Closes Issue #255 Ignore large gzipped files. 174 | 2018-10-12 Richard Forth Closes Issue #268 Additional verbiage about 175 | maxclients/maxrequestworkers being hit only 176 | once per lifetime. 177 | 178 | For more information see: 179 | https://github.com/apache/httpd/blob/0b61edca6cdda2737aa1d84a4526c5f9d2e23a8c/server/mpm/prefork/prefork.c#L809 180 | 2018-10-12 Richard Forth Closes Issue #241 --no-check-pid causes perl 181 | error. 182 | 2018-09-26 Andriy Fetsyuk Merged a change to support more environments. 183 | 2018-08-30 Richard Forth Issue #259 fix fpm support for ubuntu systems. 184 | 2018-08-28 Richard Forth Issue #259 add support for ubuntu 18.04 185 | 2018-05-11 Richard Forth Issue #253 need to specify a pid to bypass the 186 | 'Multple PIDS listeing on port 80' error. 187 | 2018-02-10 Richard Forth Addressed a concern over plesk vhosts counting 188 | now ommiting domains containing: 189 | - lists 190 | - default 191 | - webmail 192 | from the count. 193 | 2017-12-05 Richard Forth Closes #231 Respect ServerLimit. 194 | 2017-11-01 Richard Forth Closes #229 Removed easter egg, causing problems. 195 | 2017-10-13 Richard Forth Closes #223 Added an easter egg. 196 | To trigger it, set maxclients / 197 | maxrequestworkers to 5 or less. 198 | 2017-10-13 Richard Forth Closes #222 pidfile not found when ecased in quotes. 199 | 2017-10-13 Richard Forth Added some much needed carriage returns to VERBOSE lines. 200 | 2017-07-14 Richard Forth Resolved #215, pidfile issue. 201 | 2017-07-08 Richard Forth Resolved #195, virtualmin/webmin pidfile issue. 202 | 2017-07-08 Richard Forth Resolved #211, detect virtualmin/webmin install. 203 | Note: only displays webmin version in virtualmin 204 | installs. 205 | 2017-07-08 Richard Forth Resolved #209, Yet more ubuntu madness... 206 | No /etc/php/7.0/apache2 folder, this is 207 | /etc/php/7.0/fpm. Added logic to include this. 208 | 2017-07-08 Richard Forth Resolved #207, Hard Fail in OS is not listed 209 | as supported. 210 | 2017-07-08 Richard Forth Resolved #206, Drop support for debian 7 211 | Issues with perl modules out of the box. 212 | 2017-07-08 Richard Forth Resolved #205 at least on Debian 9 213 | debian update check broken, requires python 214 | Addded a new preflight check to check for a 215 | python binary. 216 | Also adjusted the way to check what OS we are 217 | running as debian 9 now reports its distro in 218 | all lowercase. 219 | 2017-07-08 Richard Forth Resolved #197 at least on Debian 9 220 | debian php.ini using cli not apache 221 | committed to staging for further testing 222 | 2017-07-08 Richard Forth Closes #204, Debian 9 pidfile not found error. 223 | 2017-07-07 Richard Forth Closes #201, a serious bug that invoked OOMKILLER when 224 | scanning very large log files. 225 | 2017-06-13 Richard Forth Fixed uptime logic problem. 226 | 2017-06-13 Richard Forth Added more verbosity. 227 | 2017-04-27 Richard Forth Added more verbosity, fixed some typos. 228 | 2017-04-20 Richard Forth Closes #184 , Modified PHP Fatal Error Check. 229 | - Now works on subfolders correctly 230 | - removes need for backticked grep 231 | - cleaner and more structured summaries rather 232 | than actual log output. 233 | 2017-04-20 Richard Forth Closes #183 , Added skip checks for the following: 234 | --skip-maxclients 235 | --skip-php-fatal 236 | --skip-updates 237 | See --help for more information. 238 | 2017-04-04 Richard Forth Added 'Red Hat Enterprise Linux Server' to new 239 | list of supported OSes. Fixes #179. Bug. 240 | 2017-04-02 Richard Forth Added code to detect older EOL OS versions and abort. 241 | Still in soft-fail at the moment. 242 | 2017-04-02 Richard Forth Added code to detect EOL OS versions and abort. 243 | Still in soft-fail at the moment. 244 | 2017-04-02 Richard Forth Laid foundations for new OS detection using 245 | some embedded python, this actually reduces 246 | required code base by over 100 lines of code, 247 | missing OS verification checks now but will 248 | fix this in next update. 249 | 2017-03-28 Richard Forth Small bug fix. 250 | 2017-03-25 Richard Forth Improved regex around the vhost count, specifically 251 | when looking for "port" it was capturing 252 | aliases etc that has the word port as part of it, like 253 | support.mydomain.com, the regex should filter out what 254 | we dont want to see and only give us a true count of 255 | vhosts. Closes #160. 256 | 2017-03-25 Richard Forth Downgraded some warnings to advisory messages. 257 | Specifically where vhost count is greater than 258 | MaxClients / MaxRequestWorkers, also changed 259 | the wording slightly. 260 | 2017-03-25 Richard Forth Added a key to the usage() output, see --help 261 | 2017-03-25 Richard Forth Fixed some warnings and advisory messages not 262 | being hidden when using --nowarn or --report oprtions. 263 | 2017-03-25 Richard Forth Made the commands I think that are likely to 264 | fail due to different locale settings run in 265 | en_GB.UTF-8 which doesnt affect system locale 266 | Also removed obsolete locale code that didnt 267 | do the job I wanted, also removed another 268 | 'privado' word I found, as this is now 269 | obsolete. 270 | 2017-03-25 Richard Forth Minor formatting issue in vhost breakdowns. 271 | Specifically removed extra space before 272 | variable in output. 273 | 2017-03-24 Richard Forth Fixed vhost breakdowns for when $real_port is 274 | not defined. 275 | 2017-03-24 Richard Forth Fixed vhost breakdowns for when apache is not 276 | listening on port 80, but a custom port. 277 | 2017-03-24 Richard Forth Fixed some issues with vhost count breakdowns. 278 | 2017-03-24 Richard Forth Removed 'privado' word, as locale setting makes this 279 | redundant. 280 | 2017-03-24 Richard Forth Added vhost count breakdown for HTTP/HTTPS, 281 | closes #142 282 | 2017-03-24 Richard Forth Removed 'OUCA' word, as locale setting makes this 283 | redundant. 284 | 2017-03-24 Richard Forth Worked out that I can actually set a locale 285 | without affecting the system locale, closes 286 | #144 287 | 2017-03-07 Richard Forth Added Verbose message to start of PHP Fatal 288 | Checks, Closes #139 289 | 2017-02-28 Richard Forth Added en_AU to locale Preflight Check, Closes #137 290 | 2017-02-24 Richard Forth Preflight Check requires en_US or en_GB, Closes #127 291 | 2017-02-24 Richard Forth PHP No longer a hard requirement. Closes #128 292 | 2017-02-24 Richard Forth Removed blocking code when PHP binary not found. 293 | 2017-02-09 Richard Forth Removed some obsolete lines of code. 294 | 2017-02-05 Richard Forth Moved all the github messaging into comments 295 | at the top of the file, rather than in output. 296 | 2017-02-05 Richard Forth Added wiki page and better documentation and 297 | error messaging around the parent PID, if the 298 | script exists because it is greater than 50MB 299 | - addresses issue #110 300 | 2017-02-03 Richard Forth Changed the logo for a more subtle and 301 | professional, simple, and clean look. 302 | 2017-01-28 Richard Forth Fixed #102 rpm error on ubuntu systems. 303 | - hardcoded "MySQL" in to detection string rather than 304 | adding lines and lines of unnecessary code trying to 305 | get the actual MySQL variant. I decided it wasnt that 306 | important to warrant the extra payload trying to 307 | ascertain the right OS. 308 | - this change backs out the fix used to address issue 309 | #62 (MariaDB incorrectly identified as MySQL). 310 | 2017-01-28 Richard Forth Fixed #103 Spelling mistake. 311 | 2017-01-26 Richard Forth Fix version support cleanup and verbosity fixes. 312 | 2017-01-26 Richard Forth Fixed some punctuation issues and color warning. 313 | 2017-01-26 Richard Forth Fixed some breakages on Centos 5, 6, 7 314 | - Added a warning that Centos 5 / RHEL 5 is being 315 | deprecated / EOLed in March 2017, so after this time 316 | I will not be supporting CentOS5 any more. 317 | 2017-01-65 Richard Forth Fixed #88 Unable to determine whether Apache is using 318 | worker or prefork 319 | 2017-01-25 Richard Forth Fixed #84 Unable to open pidfile. 320 | - Changed up the logic to check if the 321 | setting is in fact a file BEFORE 'guessing'. 322 | 2017-01-25 Richard Forth MPM ITK detection and exit. 323 | 2017-01-25 Richard Forth Fixed #85 Wrong MPM information 324 | 2017-01-19 Richard Forth Fixed #82 bug with calculations displaying wrong 325 | colours 326 | 2017-01-14 Richard Forth Updated logo 327 | - updated tagline 328 | - added a cool intro 329 | - Fixed issue #62 MariaDB wrongly detected as 330 | MySQL, this also detects percona correctly 331 | too. 332 | - Finally fixed the #57 issue regarding wonky 333 | arrows in the report 334 | - Fixed the #66 issue regarding extraneous 335 | information in --report output 336 | - Fixed futher issues relating to the #66 issue 337 | regarding extraneous information in --report output 338 | - Added a temp workaround for #69 339 | - Fixed #69 by ripping out the cool typing 340 | emulation as it breaks on Red Hat based 341 | systems less than CentOS7 342 | - Fixed #70 missing newline in large log 343 | - Fixed #77 CRITICAL should be ADVISORY 344 | - Fixed color problems related to #77 345 | 2017-01-13 Richard Forth Addressed issue #9 Find Large Logs 346 | - Mopped up some code and made the additional 347 | services check look a bit more polished.. 348 | - Addressed issue #25 PHP-FPM Logs 349 | - Addressed issue #48 Plesk logs (warning) 350 | - Addressed issue #49 Detect Plesk 351 | - Addressed issue #50 Detect icPanel 352 | - Code cleanup and output polishing 353 | - Addressed issue #3 Detect Package Updates 354 | - Addressed issue #55 bug in package updates 355 | on debian and ubuntu systems 356 | - Addressed issue #55 bug in package updates 357 | - Addressed issue #56 Detect PHP Updates 358 | - Added more directories to check for large 359 | log files: 360 | - /usr/local/httpd/logs 361 | - /usr/local/apache/logs 362 | - /usr/local/apache2/logs 363 | Will silently continue if these folders dont 364 | exist. 365 | - Addressed Issue with wonky arrows, still 366 | not happy with it though, columns are out of 367 | whack. #OCD, will raise issue and work it 368 | proper. 369 | 2017-01-13 Richard Forth Addressed issue #34 Added more graceful 370 | message when apache[2]ctl is not found. 371 | 2017-01-13 Richard Forth Addressed issue #33 Removed Version Number 372 | 2016-12-06 Richard Forth Addressed issue #23 Low Uptime Warnings 373 | 2016-12-05 Richard Forth Addressed issue #22 Add advisory message for 374 | php memory limit of -1 375 | 2016-11-25 Richard Forth Addressed issue #6 'Check for and report on 376 | PHP Fatal Errors in the logs' 377 | 2016-11-25 Richard Forth Merged pull request #20 "fix issues around 378 | maxrequestworkers value" by jboulen. 379 | 2016-11-25 Richard Forth Addressed Issue #5, May need further testing 380 | 2016-11-15 Richard Forth Addressed Issue #19, Confirmed Fixed. 381 | 2016-09-21 Richard Forth Addressed Issue #8, Confirmed Fixed. 382 | 2016-09-21 Richard Forth Addressed Issue #15, Confirmed Fixed. 383 | 2016-07-26 Richard Forth Bugfix for Issue #11, Confirmed Fixed. 384 | 2016-07-26 Richard Forth Bugfix for Issue #11, testing #3. Snagging. 385 | 2016-07-26 Richard Forth Bugfix for Issue #11, testing #2. To be confirmed. 386 | 2016-07-26 Richard Forth Bugfix for Issue #11, testing. To be confirmed. 387 | 2016-06-24 Richard Forth Corrected the codename for Debian 6.0 which is EOL. 388 | 2016-06-24 Richard Forth Tidied up the code for checking ubuntu LTS versions. 389 | 2016-06-22 Richard Forth Tidied up the changelog, lines overflowing corrected. 390 | 2016-06-22 Richard Forth Added exclusion for "mod" in grep for "reached" when 391 | scanning log files for lines where MaxClients / 392 | MaxRequestWorkers limit has been reached. 393 | Thanks to Kaz =) Change going into staging for 394 | him to test. 395 | 2016-06-10 Richard Forth Cleaned up old code, fixed up old news items and 396 | information messages. 397 | 2016-06-10 Richard Forth Removed 'last modified item from script, as this 398 | should be handled by github and not hardcoded into 399 | the script'. 400 | 2016-05-27 Richard Forth Emergency change due to unsafe execution when no 401 | apache process is found, this change fixes a bug 402 | that was introduced in the change made on 403 | 2016-05-24. 404 | 2016-05-27 Richard Forth Added INFO ITEM about LICENSE. 405 | 2016-05-27 Richard Forth Added LICENSE (Apache License Version 2.0). 406 | http://apache2buddy.pl/LICENSE 407 | 2016-05-27 Richard Forth Added the notice in the source code as follows: 408 | 409 | 410 | Copyright 2016 Richard Forth 411 | 412 | Licensed under the Apache License, Version 2.0 (the "License"); 413 | you may not use this file except in compliance with the License. 414 | You may obtain a copy of the License at 415 | 416 | http://www.apache.org/licenses/LICENSE-2.0 417 | 418 | Unless required by applicable law or agreed to in writing, software 419 | distributed under the License is distributed on an "AS IS" BASIS, 420 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 421 | See the License for the specific language governing permissions and 422 | limitations under the License. 423 | 424 | ----- 425 | 426 | The copyright line reads as my name, because I did a lot of work in making 427 | this fork a whole new beast, and the previous project was never distributed 428 | under any official license, I did get the blessing from Gus Maskowitz to 429 | publish it under the Apache License 2.0. 430 | 431 | The purpose of the license is to ensure that free software remains free, not 432 | as in "free beer" but in "freedom". The freedom to derive new software from 433 | this and to keep that software "free" too. 434 | 435 | But it is important to acknowldege too, that this is a derivative work from 436 | apachebuddy.pl. Even though it is not being maintained any more, I would like 437 | to acknowledge the talents of a few people: 438 | 439 | Major Hayden - for his inspiring work on MySQLTuner.pl 440 | Jacob Walcik - who is the credited author of apachebuddy.pl (see the first 441 | few lines of the original script code at http:// apachebuddy.pl) 442 | Gus Maskowitz - for his work on the script, though it is no longer maintained 443 | Will Parsons - for hosting the original script at http://apachebuddy.pl 444 | 445 | Here I note that I also do not maintain the old project, this is a complete 446 | fork and revamp of the original code, and is maintained separately. 447 | 448 | These credits will remain and will, in time, be embedded into the code of the 449 | original script as a mark of respect for the heritage of this code. 450 | 451 | Please keep any and all credits in the source code, and if you derive a new 452 | software from it, by all means add your own credits. 453 | 454 | 2016-05-24 Richard Forth Added INFO ITEM about sha256sums. 455 | 2016-05-24 Richard Forth Added sha256sums => 456 | http://apache2buddy.pl/sha256sums.txt 457 | 2016-05-24 Richard Forth Changed the way it handles nothing at all 458 | listening on port 80. It now falls back 459 | to the process list as if somehing WAS 460 | listening on port 80 that was NOT apache. 461 | 2016-05-20 Richard Forth Fixed a bug with parent pid detected, 462 | reported by J. Ramsey - with thanks! 463 | 2016-03-03 Richard Forth Added MD5Sums + News Item. 464 | 2016-02-27 Richard Forth Added code to display verbose memory 465 | usage information, and then bug out with a 466 | critical error if ram is overbooked, before 467 | we even start (ie no ram left for apache) 468 | 2016-02-27 Richard Forth Added code to display verbose memory usage 469 | information, prior to calculations, as I was 470 | trying to solve the mystery of negative 471 | percentages. Found that if ram is over booked, 472 | this skews the results massively. 473 | 2016-02-24 Richard Forth Added code to display MaxRequestsPerChild 474 | information (prefork mode) 475 | 2016-02-24 Richard Forth Fixed bug out code in preflight checks if 476 | apache parent is greater than 50MB, as 477 | I was notified that this wasnt working on 478 | ubuntu 12.04 systems. 479 | 2016-02-16 Richard Forth Added bug out code in preflight checks if 480 | apache parent is greater than 50MB, as 481 | this skews results and calculations. 482 | 2015-10-18 Richard Forth Fixed some logic in the Results section. 483 | 2015-10-18 Richard Forth Dropped "new and improved!!!" from the logo. 484 | Updated the news items. 485 | 2015-10-03 Richard Forth Changed Recommendations based on an actual 486 | valid range (10% of max). Previously anything 487 | below the maximim was considered accpetable. 488 | Now there is a minimum (Max / .90) and Max, 489 | which is reported in the recommendations. 490 | 2015-10-03 Richard Forth Changed layout of memory report, as requested 491 | by Luke H. 492 | 2015-10-03 Richard Forth Added detection for if maxClients or 493 | MaxRequestWorkers has been hit. 494 | 2015-09-25 Richard Forth Added Apache current memory consumption to 495 | 'Analysing apache memory usage' section. 496 | 2015-09-23 Richard Forth Added Gluster Memory Detection 497 | - Feature Request from P. Steyn 498 | 2015-09-08 Richard Forth Minor Cosmetic Changes and Spelling Corrections 499 | Removed --PHP option, and included 500 | PHP Memory Limit in Discoveries. 501 | 2015-09-03 Richard Forth Minor Cosmetic Changes and Spelling Corrections. 502 | 2015-08-21 Richard Forth Minor Bug Fix - Lines 1497 and 1498 added. 503 | 2015-08-06 Richard Forth Improvement to code. 504 | 2015-08-06 Richard Forth K. Geaney Reported a bug whereby the script 505 | would detect itself in the processlist 506 | apache2 portion of the script name 507 | apache2buddy.pl is picked up when the script 508 | is wgetted and run locally rather than with 509 | curl & perl. 510 | Added a filter (grep -v "buddy") to prevent this 511 | happening in the future. 512 | 2015-08-03 Richard Forth L. Hanley Reported a bug whereby the script 513 | would error if the apache process was 514 | running as a user that had more than 8 515 | characters. This has been fixed now. 516 | 2015-06-06 Richard Forth G. John reported a bug whereby if a server 517 | has a certain character set, the apache 518 | user is shown as 'apache\x{d}' rather than 519 | 'apache', which causes the ps aux line 520 | to produce and empty data set which is passed 521 | to pmap resulting in a division by 522 | zero error, this was also present in the 523 | maxclients and serverlimit variables. 524 | 525 | To fix that I added the following lines of code 526 | in various places in the script: 527 | 528 | # account for 'apache\x{d}' strangeness 529 | $variable =~ s/\x{d}//; 530 | 531 | What this does is replace the control characters 532 | that cause this issue, preventing the script from 533 | producing a list of PIDs. Better error handling if 534 | the list of PIDs are still zero. 535 | Where $variable name was one of $apache_user, 536 | $maxclients, or $serverlimit. 537 | 2015-06-03 Richard Forth Switched the IP provider from curlmyip.com to 538 | myip.dnsomatic.com as curlmyip was becoming very 539 | slow to respond, causing unnecessary lag. 540 | 2015-05-27 Richard Forth Removed last 5 log entries from output 541 | if --report or --noinfo is specified. 542 | 2015-05-27 Richard Forth Removed calculations of maxclients for 543 | worker processes. Instead, I left a 544 | friendly message to go check manually for 545 | backend processes such as PHP-FPM 546 | and its pm.max_children directive. 547 | There is no plan to add functionality to 548 | check this, as it will become a scope creep. 549 | 2015-05-27 Richard Forth Bug Fix reported by C. Piper-Balta in which 550 | the script would produce errors if 551 | the process name was /usr/sbin/httpd.worker 552 | or /usr/sbin/nginx. The test_process 553 | subroutine had some flawed logic which has 554 | been fixed now. 555 | 2015-04-23 Richard Forth Bug Fixes when selecting --lightbg / -L, 556 | not all colours were made bold. 557 | 2015-04-23 Richard Forth Bug Fixes when falling back to process list, 558 | now picks up the correct port. 559 | 2015-04-23 Richard Forth Minor cosmetic changes. 560 | 2015-04-23 Richard Forth Added more graceful operation if Varnish is 561 | found on port 80 instead of apache, it 562 | now is able to go away and find out what 563 | port apache is really listening on, eg 564 | port 8080, and reset itself to go ahead and 565 | continue the scan on the new port. 566 | 2015-04-17 Richard Forth Added support for RHEL 7 / Apache 2.4 567 | 2015-04-17 Richard Forth Added support for CentOS 7 / Apache 2.4 568 | 2015-04-17 Richard Forth Minor bugfixes, made enhancements to reporting, 569 | cleaned up the news items.. 570 | 2015-04-17 Richard Forth Minor bugfixes, added some missing carriage 571 | returns, nothing major. 572 | 2015-04-16 Richard Forth Added support for Scientific Linux 6.6. 573 | 2015-04-15 Richard Forth Change project status from UNSTABLE to 574 | STABLE. Please report any errors. 575 | 2015-04-15 Richard Forth Added support for Debian 7. 576 | 2015-04-15 Richard Forth Added helpful error messages when apache is not 577 | found to be started or installed. 578 | 2015-04-15 Richard Forth Added support for Debian 6. 579 | 2015-04-15 Richard Forth Corrected an error on Ubuntu systems where it 580 | would incorrectly suggest that 'worker' was not 581 | a supported MPM, it is, because sometimes people 582 | will use worker when using php-fpm. 'event' 583 | however is not supported, but I added a 584 | nice helpful message if event MPM is detected on 585 | how to switch to a supported MPM. 586 | 2015-04-15 Richard Forth Added better error-handling for missing php package 587 | on ubuntu and prints a helpful message. 588 | 2015-04-15 Richard Forth Added better error-handling for missing modules 589 | on ubuntu 12.04 and prints a helpful message. 590 | 2015-04-14 Richard Forth Added NEWS item, plugging the new support for 591 | Ubuntu systems. 592 | 2015-04-14 Richard Forth Removed support for the event MPM. 593 | 2015-04-14 Richard Forth Added support for Ubuntu, requires further testing 594 | but looking good. 595 | 2015-04-14 Richard Forth Added MPM to report section. 596 | 2015-04-13 Richard Forth Added hostname and IP address to report section, 597 | this makes a lot of sense. If we're looping through 598 | a server farm we need toidentify each one, right? 599 | 2015-04-13 Richard Forth Split "Analysing your memory usage" into 2 sections, 600 | the second section is called "Results". 601 | 2015-04-13 Richard Forth Merged two sections "Pre-Flight Checks" and 602 | "Analysing your apache configuration", into 603 | "Performing Auto-Discovery and Pre-Flight Checks" 604 | 2015-04-13 Richard Forth Converting large blocks of print statements: 605 | header done. 606 | 2015-04-12 Richard Forth Fixed some inconsistencies with heading colours. 607 | 2015-04-12 Richard Forth Converting large blocks of print statements 608 | into heredocs, to shave off more deadweight. 609 | Usage done so far. 610 | 2015-04-12 Richard Forth Fixed a bug where it would display warnings 611 | about ThreadsPerChild regardless of the model, 612 | I made it so it only displays the warning if 613 | indeed the current model is worker. 614 | 2015-04-12 Richard Forth - added --report|r option, awaiting feedback. 615 | ================================================================================================================== 616 | Previous... 617 | - cleaning up code. 618 | - switching to ANSI escape codes for colors to cut down the file size. 619 | - generalling getting it stable, 620 | 621 | -------------------------------------------------------------------------------- /apache2buddy.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | eval "use diagnostics; 1" or die("\n[ FATAL ] Could not load the Diagnostics module.\n\nTry:\n\n sudo apt-get install perl-modules\n\nThen try running this script again.\n\n"); 4 | use Getopt::Long qw(:config no_ignore_case bundling pass_through); 5 | use POSIX; 6 | use strict; 7 | use File::Find; 8 | ############################################################################################################ 9 | # __ ___ __ __ __ __ 10 | # ____ ____ ____ ______/ /_ ___ |__ \ / /_ __ ______/ /___/ /_ __ ____ / / 11 | # / __ `/ __ \/ __ `/ ___/ __ \/ _ \__/ // __ \/ / / / __ / __ / / / / / __ \/ / 12 | # / /_/ / /_/ / /_/ / /__/ / / / __/ __// /_/ / /_/ / /_/ / /_/ / /_/ / / /_/ / / 13 | # \__,_/ .___/\__,_/\___/_/ /_/\___/____/_.___/\__,_/\__,_/\__,_/\__, (_) .___/_/ 14 | # /_/ /____/ /_/ 15 | # 16 | ############################################################################################################ 17 | # author: richard forth 18 | # description: apache2buddy, a fork of apachebuddy that caters for apache2, obviously. 19 | # 20 | # Github Page: https://github.com/richardforth/apache2buddy 21 | # Please only make pull requests from staging branch. 22 | # 23 | # [ INFO ] apache2buddy.pl is a fork of apachebuddy.pl. 24 | # [ INFO ] MD5SUMs now availiable at https://raw.githubusercontent.com/richardforth/apache2buddy/master/md5sums.txt 25 | # [ INFO ] SHA256SUMs now availiable at https://raw.githubusercontent.com/richardforth/apache2buddy/master/sha256sums.txt 26 | # [ INFO ] apache2buddy.pl is now released under the Apache 2.0 License. See https://raw.githubusercontent.com/richardforth/apache2buddy/master/LICENSE 27 | # [ INFO ] apache2buddy.pl is now hosted from github. See https://github.com/richardforth/apache2buddy 28 | # [ INFO ] Changelogs and updates in github. See https://raw.githubusercontent.com/richardforth/apache2buddy/master/changelog 29 | # 30 | ########################################################################################################### 31 | # 32 | # L I C E N S E 33 | # 34 | ########################################################################################################### 35 | # 36 | # Copyright 2016 Richard Forth 37 | # 38 | # Licensed under the Apache License, Version 2.0 (the "License"); 39 | # you may not use this file except in compliance with the License. 40 | # You may obtain a copy of the License at 41 | # 42 | # http://www.apache.org/licenses/LICENSE-2.0 43 | # 44 | # Unless required by applicable law or agreed to in writing, software 45 | # distributed under the License is distributed on an "AS IS" BASIS, 46 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 47 | # See the License for the specific language governing permissions and 48 | # limitations under the License. 49 | # 50 | # The copyright line reads as my name, because I did a lot of work in making 51 | # this fork a whole new beast, and the previous project was never distributed 52 | # under any official license, I did get the blessing from Gus Maskowitz to 53 | # publish it under the Apache License 2.0. 54 | # 55 | # The purpose of the license is to ensure that free software remains free, not 56 | # as in "free beer" but in "freedom". The freedom to derive new software from 57 | # this and to keep that software "free" too. 58 | # 59 | # But it is important to acknowldege too, that this is a derivative work from 60 | # apachebuddy.pl. Even though it is not being maintained any more, I would like 61 | # to acknowledge the talents of a few people: 62 | # 63 | # Major Hayden - for his inspiring work on MySQLTuner.pl 64 | # Jacob Walcik - who is the credited author of apache2buddy.pl (see the first 65 | # few lines of the original script code at http:// apachebuddy.pl) 66 | # Gus Maskowitz - for his work on the script, though it is no longer maintained 67 | # Will Parsons - for hosting the original script at http://apachebuddy.pl 68 | # 69 | # Here I note that I also do not maintain the old project, this is a complete 70 | # fork and revamp of the original code, and is maintained separately. 71 | # 72 | # Please keep any and all credits in the source code, and if you derive a new 73 | # software from it, by all means add your own credits. 74 | # 75 | # 76 | ############################################################################################################ 77 | # D I S C L A I M E R 78 | ############################################################################################################ 79 | # 80 | # PLEASE NOTE THAT THIS IS DESIGNED AS A REPORTING TOOL ONLY 81 | # ============================================================== 82 | # 83 | # There are no plans to make this script automatically change any apache configuration settings, as this 84 | # could prove disasterous in production environments. 85 | # 86 | # Needless to say this script comes without any warranty or fitness for a particular use and you run it 87 | # at your own risk. 88 | # 89 | # * * * * USE THIS SCRIPT AT YOUR OWN RISK * * * * 90 | # 91 | # Every care has been taken to make sure this does nothing bad, it should only be reading configuration 92 | # files, and doing a bit of maths, and printing out a shiny report. 93 | # 94 | # Nothing more nothing less. 95 | # 96 | # Please pay special attention and review the source code before deciding whether you should run this on 97 | # your systems. 98 | # 99 | # I am not responsible for any damage caused to your data, systems or hardware, loss of business, your 100 | # marriage breaking down, getting your car or house reposessed, or ending up in prison or losing your job 101 | # and drug habits developing , insolvency, bankruptcy, personal hygiene issues, disease or death that may 102 | # stem as a direct result of running this script. 103 | # 104 | # YOU HAVE BEEN WARNED! 105 | # 106 | # 107 | ########################################################################################################### 108 | # 109 | # Note: 110 | # 111 | # If you really want to read all of the source code, I suggest that you start from the comment: 112 | # 113 | # ######################## 114 | # # BEGIN MAIN EXECUTION # 115 | # ######################## 116 | # 117 | # Because everything from this point to that, is just a bunch of subroutines that the script needs in order 118 | # to be able to run, and it won't flow or make any sense until you start from the "start". 119 | # 120 | ########################################################################################################## 121 | 122 | ## print usage 123 | sub usage { 124 | our $usage_output = <<'END_USAGE'; 125 | 126 | Usage: apache2buddy.pl [OPTIONS] 127 | If no options are specified, the basic tests will be run. 128 | 129 | -h, --help Print this help message 130 | -p, --port=PORT Specify an alternate port to check (default: 80) 131 | --pid=PID Specify a PID to bypass the "Multiple PIDS listening on port 80" error. 132 | -v, --verbose Use verbose output (this is very noisy, only useful for debugging) 133 | -n, --nocolor Use default terminal color, dont try to be all fancy! 134 | -H, --noheader Do not show header title bar. 135 | -N, --noinfo Do not show informational messages. 136 | -K, --no-ok Do not show OK messages. 137 | -W, --nowarn Do not show warning messages. 138 | -L, --light-term Show colours for a light background terminal. 139 | -r, --report Implies -HNWK or --noinfo --nowarn --no-ok --noheader --skip-maxclients --skip-php-fatal --skip-updates 140 | -P, --no-check-pid DON'T Check the Parent Pid File Size (only use if desperate for more info, results may be skewed). 141 | --skip-maxclients Skip checking in maxclients was hit recently, can be slow, especialy if you have large log files. 142 | --skip-php-fatal Skip checking for PHP FATAL errors, can be slow, especialy if you have large log files. 143 | --skip-updates Skip checking for package updates, can be slow or problematic, causing the script to hang. 144 | -O, --skip-os-version-check Skips past the OS version check. 145 | Allows one to bypass EOL version showstopper but be mindful: 146 | skipping the os version check is not recommended as features may be 147 | deprecated or removed and apache2buddy is not backward compatible 148 | with end of life operating systems, this may cause errors and unpredictable 149 | behaviour. 150 | 151 | 152 | Key: 153 | 154 | [ -- ] = Information 155 | [ @@ ] = Advisory 156 | [ >> ] = Warning 157 | [ !! ] = Critical 158 | 159 | 160 | END_USAGE 161 | 162 | print $usage_output; 163 | } 164 | ######################## 165 | # GATHER CMD LINE ARGS # 166 | ######################## 167 | 168 | # if help is not asked for, we do not give it 169 | my $help = ""; 170 | 171 | # by default, assume the terminal has dark background, eg putty 172 | my $LIGHTBG = 0; 173 | 174 | # if no port is specified, we default to 80 175 | my $port = 80; 176 | 177 | # if no pid is specified, we default to 0 178 | our $pid = 0; 179 | 180 | # by default, do not use verbose output 181 | our $VERBOSE = ""; 182 | 183 | # by default, use color output 184 | our $NOCOLOR = 0; 185 | 186 | # by default, show news messages 187 | our $NONEWS = 0; 188 | 189 | # by default, show informational messages 190 | our $NOINFO = 0; 191 | 192 | # by default, show ok messages 193 | our $NOOK = 0; 194 | 195 | # by default, show warnings 196 | our $NOWARN = 0; 197 | 198 | # by default, show full output 199 | our $REPORT = 0; 200 | 201 | # by default, show header 202 | our $NOHEADER = 0; 203 | 204 | # by default, check pid size 205 | our $NOCHKPID = 0; 206 | 207 | # by default, check OS support 208 | our $NOCHKOS = 0; 209 | 210 | # add 'skip section' options... 211 | 212 | # by default, do not skip maxclients check 213 | our $SKIPMAXCLIENTS = 0; 214 | 215 | # by default, do not skip php fatal errors check 216 | our $SKIPPHPFATAL = 0; 217 | 218 | # by default, do not skip updates check 219 | our $SKIPUPDATES = 0; 220 | 221 | ###################### 222 | # MORE OUR VARIABLES # 223 | ###################### 224 | 225 | # "cache" for os platform information: ( distro, version, codename ) 226 | our @os_platform; 227 | 228 | # For ss and netstat 229 | our $ss_path; 230 | our $netstat_path; 231 | 232 | 233 | # grab the command line arguments 234 | GetOptions( 235 | 'help|h' => \$help, 236 | 'port|p:i' => \$port, 237 | 'pid:i' => \$pid, 238 | 'verbose|v' => \$VERBOSE, 239 | 'nocolor|n' => \$main::NOCOLOR, 240 | 'noinfo|N' => \$NOINFO, 241 | 'nowarn|W' => \$NOWARN, 242 | 'report|r' => \$REPORT, 243 | 'light-term|L' => \$LIGHTBG, 244 | 'no-ok|K' => \$NOOK, 245 | 'noheader|H' => \$NOHEADER, 246 | 'no-check-pid|P' => \$NOCHKPID, 247 | 'skip-maxclients' => \$SKIPMAXCLIENTS, 248 | 'skip-php-fatal' => \$SKIPPHPFATAL, 249 | 'skip-updates' => \$SKIPUPDATES, 250 | 'skip-os-version-check|O' => \$NOCHKOS, 251 | 'nonews' => \$NONEWS 252 | ); 253 | 254 | # check for invalid options, bail if we find any and print the usage output 255 | if ( @ARGV > 0 ) { 256 | print "Invalid option: "; 257 | foreach (@ARGV) { 258 | print $_." "; 259 | } 260 | print "\n"; 261 | usage; 262 | exit 1; 263 | } 264 | 265 | if ( $REPORT ) { 266 | $NOHEADER = 1; 267 | $NOINFO = 1; 268 | $NONEWS = 1; 269 | $NOWARN = 1; 270 | $NOOK = 1; 271 | $SKIPMAXCLIENTS = 1; 272 | $SKIPPHPFATAL = 1; 273 | $SKIPUPDATES = 1; 274 | } 275 | 276 | # Declare constants such as ANSI COLOR schemes. 277 | # TO SAVE CODE and thus also massively cut down on the file size, Ive decided to handle NOCOLOR here, 278 | # instead of on every single line where I need to print something. it seems much simpler and cleaner 279 | # to print an empty string rather than an escape sequence, rather than doubling up on print() statements. 280 | our $RED; 281 | our $GREEN; 282 | our $YELLOW; 283 | our $BLUE; 284 | our $PURPLE; 285 | our $CYAN; 286 | our $ENDC; 287 | our $BOLD; 288 | our $UNDERLINE; 289 | if ( ! $NOCOLOR ) { 290 | if ( ! $LIGHTBG ) { 291 | $RED = "\033[91m"; 292 | $GREEN = "\033[92m"; # Like a light green color, not good for light terminals, but perfect for dark, eg PuTTY, terminals. 293 | $YELLOW = "\033[93m"; # Like a yellow color, not good for light terminals, but perfect for dark, eg PuTTY, terminals. 294 | $BLUE = "\033[94m"; 295 | $PURPLE = "\033[95m"; # technically its Magento... 296 | $CYAN = "\033[96m"; # Like a light blue color, not good for light terminals, but perfect for dark, eg PuTTY, terminals. 297 | } else { 298 | $RED = "\033[1m"; # bold all the things! 299 | $GREEN = "\033[1m"; # bold all the things! 300 | $YELLOW = "\033[1m"; # bold all the things! 301 | $BLUE = "\033[1m"; # bold all the things! 302 | $PURPLE = "\033[1m"; # bold all the things! 303 | $CYAN = "\033[1m"; # bold all the things! 304 | } 305 | $ENDC = "\033[0m"; # reset the terminal color 306 | $BOLD = "\033[1m"; # what it says on the tin, you can double up eg make a bold red: ${BOLD}${RED}Something${ENDC} 307 | $UNDERLINE = "\033[4m"; # again you can double this one up. 308 | } else { 309 | $RED = ""; # SUPPRESS COLORS 310 | $GREEN = ""; # SUPPRESS COLORS 311 | $YELLOW = ""; # SUPPRESS COLORS 312 | $BLUE = ""; # SUPPRESS COLORS 313 | $PURPLE = ""; # SUPPRESS COLORS 314 | $CYAN = ""; # SUPPRESS COLORS 315 | $ENDC = ""; # SUPPRESS COLORS 316 | $BOLD = ""; # SUPPRESS COLORS 317 | $UNDERLINE = ""; # SUPPRESS COLORS 318 | } 319 | 320 | sub get_os_platform { 321 | return @main::os_platform if @main::os_platform; # we already know everything 322 | 323 | my ($distro, $version, $codename); 324 | 325 | my %os_info; 326 | 327 | # Parse /etc/os-release if available 328 | if (-e "/etc/os-release") { 329 | open my $fh, '<', '/etc/os-release' or return ('Unknown', undef, undef); 330 | while (<$fh>) { 331 | chomp; 332 | if (/^(\w+)=(?:"|')?(.+?)(?:"|')?$/) { 333 | $os_info{$1} = $2; 334 | } 335 | } 336 | close $fh; 337 | 338 | $distro = $os_info{NAME}; 339 | $version = $os_info{VERSION_ID}; 340 | # add an escape hatch for Gentoo which doesnt use codenames 341 | if ($distro eq "Gentoo") { 342 | $codename = "unknown"; 343 | } else { 344 | $codename = $os_info{VERSION_CODENAME} || ($os_info{VERSION} =~ /\(([^)]+)\)/ ? $1 : undef); 345 | } 346 | 347 | } 348 | 349 | # Fallback: /etc/lsb-release 350 | if (!$distro && -e "/etc/lsb-release") { 351 | open my $fh, '<', '/etc/lsb-release' or return ('Unknown', undef, undef); 352 | while (<$fh>) { 353 | chomp; 354 | if (/^DISTRIB_ID=(.+)/) { $distro = $1; } 355 | if (/^DISTRIB_RELEASE=(.+)/) { $version = $1; } 356 | if (/^DISTRIB_CODENAME=(.+)/) { $codename = $1; } 357 | } 358 | close $fh; 359 | } 360 | 361 | # Debian-specific fallback 362 | if (!$distro && -e "/etc/debian_version") { 363 | chomp($version = `cat /etc/debian_version`); 364 | $distro = "Debian"; 365 | } 366 | 367 | # RedHat/CentOS fallback 368 | if (!$distro && -e "/etc/redhat-release") { 369 | my $line = `cat /etc/redhat-release`; 370 | if ($line =~ /^(\w+)[^\d]*(\d[\d.]*)/) { 371 | $distro = $1; 372 | $version = $2; 373 | } else { 374 | $distro = $line; 375 | } 376 | } 377 | 378 | # Gentoo fallback 379 | if (!$distro && -e "/etc/gentoo-release") { 380 | my $line = `cat /etc/gentoo-release`; 381 | if ($line =~ /Gentoo.*?(\d{4}\.\d+)/) { 382 | $distro = "Gentoo"; 383 | $version = $1; 384 | } else { 385 | $distro = "Gentoo"; 386 | } 387 | } 388 | 389 | # macOS (Darwin) 390 | if (!$distro && $^O eq 'darwin') { 391 | chomp($distro = `sw_vers -productName`); 392 | chomp($version = `sw_vers -productVersion`); 393 | } 394 | 395 | # Bitnami detection 396 | if (-d "/opt/bitnami") { 397 | $distro = "Bitnami"; 398 | # Append base distro if known 399 | if ($os_info{NAME}) { 400 | $distro .= " ($os_info{NAME})"; 401 | } 402 | } 403 | 404 | # Fallback to Perl OS name 405 | $distro ||= $^O; 406 | 407 | # ensure we return a codename even if there is none 408 | $codename ||= 'unknown'; 409 | 410 | return ($distro, $version, $codename); 411 | } 412 | sub check_os_support { 413 | my ($distro, $version, $codename) = @_; 414 | # Please dont make pull requests to add your distro to this list, that doesnt make it supported. 415 | # The following distros are what I use to test and deploy apache2buddy and only these distro's are supported. 416 | my @supported_os_list = ('Ubuntu', 417 | 'ubuntu', 418 | 'Debian', 419 | 'debian', 420 | 'Debian GNU/Linux', 421 | 'Bitnami', 422 | 'Bitnami (Debian GNU/Linux)', 423 | 'Red Hat Enterprise Linux', 424 | 'Red Hat Enterprise Linux Server', 425 | 'redhat', 426 | 'Rocky Linux', 427 | 'AlmaLinux', 428 | 'Amazon Linux', 429 | 'Oracle Linux Server'); 430 | my %sol = map { $_ => 1 } @supported_os_list; 431 | 432 | my @ubuntu_os_list = ('Ubuntu', 'ubuntu'); 433 | my %uol = map { $_ => 1 } @ubuntu_os_list; 434 | 435 | my @amazon_os_list = ('AmazonLinux', 'Amazon Linux'); 436 | my %amzol = map { $_ => 1 } @amazon_os_list; 437 | 438 | my @debian_os_list = ('Debian', 'debian', 'Bitnami', 'Bitnami (Debian GNU/Linux)'); 439 | my %dol = map { $_ => 1 } @debian_os_list; 440 | 441 | my @redhat_os_list = ('Red Hat Enterprise Linux', 'redhat', 'Rocky Linux', 'AlmaLinux'); 442 | my %rol = map { $_ => 1 } @redhat_os_list; 443 | 444 | my @oracle_os_list = ('Oracle Linux Server'); 445 | my %ool = map { $_ => 1 } @oracle_os_list; 446 | 447 | # https://wiki.debian.org/DebianReleases 448 | my @debian_supported_versions = ('12'); 449 | my %dsv = map { $_ => 1 } @debian_supported_versions; 450 | 451 | # https://www.ubuntu.com/info/release-end-of-life 452 | my @ubuntu_supported_versions = ('18.04','20.04','22.04','24.04'); 453 | my %usv = map { $_ => 1 } @ubuntu_supported_versions; 454 | 455 | # https://endoflife.date/amazon-linux 456 | my @amazon_supported_versions = ('2', '2023'); 457 | my %amznsv = map { $_ => 1 } @amazon_supported_versions; 458 | 459 | # https://endoflife.date/oracle-linux 460 | my @oracle_supported_versions = ('8.10','9.5'); 461 | my %osv = map { $_ => 1 } @oracle_supported_versions; 462 | 463 | 464 | if (exists($sol{$distro})) { 465 | if ( ! $NOOK ) { show_ok_box(); print "This distro is supported by apache2buddy.pl.\n" } 466 | # If the OS is deemed unsupported, we still run, but you may get errors, however any github issues raised will not 467 | # be entertained for unsupported or EOL OS releaases. 468 | if (exists($dol{$distro})) { 469 | my @debian_version = split('\.', $version); 470 | if ( $VERBOSE ) { 471 | foreach my $item (@debian_version) { 472 | print "VERBOSE: ". $item . "\n"; 473 | } 474 | } 475 | my $major_debian_version = $debian_version[0]; 476 | if (exists($dsv{$major_debian_version})) { 477 | if ( ! $NOOK ) { show_ok_box(); print "This distro version is supported by apache2buddy.pl.\n" } 478 | } else { 479 | show_crit_box(); print "${RED} ERROR: This distro version (${CYAN}$version${ENDC}${RED}) is not supported by apache2buddy.pl.${ENDC}\n"; 480 | # list supported debian versions 481 | if ( ! $NOINFO ) { show_advisory_box(); print "${YELLOW}Supported Debian versions:${ENDC} '${CYAN}" . join("${ENDC}', '${CYAN}", @debian_supported_versions) . "${ENDC}'. To run anyway (at your own risk), try -O or --skip-os-version-check.\n"} 482 | exit 1; 483 | } 484 | } elsif (exists($uol{$distro})) { 485 | if (exists($usv{$version})) { 486 | if ( ! $NOOK ) { show_ok_box(); print "This distro version is supported by apache2buddy.pl.\n" } 487 | } else { 488 | show_crit_box(); print "${RED}ERROR: This distro version (${CYAN}$version${ENDC}${RED}) is not supported by apache2buddy.pl.${ENDC}\n"; 489 | # list supported debian versions 490 | if ( ! $NOINFO ) { show_advisory_box(); print "${YELLOW}Supported Ubuntu (LTS ONLY) versions:${ENDC} '${CYAN}" . join("${ENDC}', '${CYAN}", @ubuntu_supported_versions) . "${ENDC}'. To run anyway (at your own risk), try -O or --skip-os-version-check.\n"} 491 | exit 1; 492 | } 493 | } elsif (exists($rol{$distro})) { 494 | # for red hat versions is not so clinical regarding the specific versions, however we need to be mindful of EOL versions eg RHEL 3, 4, 5, 6 495 | # get major version from version string. note that redhatm centos and scientifc are al rebuilds of the same sources, variables therefore 496 | # use the generic 'redhat' reference. 497 | if ( $VERBOSE ) { print "VERBOSE -> RedHat Version: ". $version . "\n"} 498 | my @redhat_version = split('\.', $version); 499 | if ( $VERBOSE ) { 500 | foreach my $item (@redhat_version) { 501 | print "VERBOSE: ". $item . "\n"; 502 | } 503 | } 504 | my $major_redhat_version = $redhat_version[0]; 505 | if ( $VERBOSE ) { print "VERBOSE -> Major RedHat Version Detected ". $major_redhat_version . "\n"} 506 | if ($major_redhat_version lt 7 ) { 507 | show_crit_box(); print "${RED}ERROR: This distro version (${CYAN}$version${ENDC}${RED}) is not supported by apache2buddy.pl.${ENDC}\n"; 508 | exit 1; 509 | } else { 510 | if ( ! $NOOK ) { show_ok_box(); print "This distro version is supported by apache2buddy.pl.\n" } 511 | } 512 | } elsif (exists($amzol{$distro})) { 513 | if ( $VERBOSE ) { print "VERBOSE -> Amazon Linux Version: ". $version . "\n"} 514 | my @amazon_version = split('\.', $version); 515 | if ( $VERBOSE ) { 516 | foreach my $item (@amazon_version) { 517 | print "VERBOSE: ". $item . "\n"; 518 | } 519 | } 520 | my $major_amazon_version = $amazon_version[0]; 521 | if ( $VERBOSE ) { print "VERBOSE -> Major Amazon Version Detected ". $major_amazon_version . "\n"} 522 | if ($major_amazon_version lt 2 ) { 523 | show_crit_box(); print "${RED}ERROR: This distro version (${CYAN}$version${ENDC}${RED}) is not supported by apache2buddy.pl.${ENDC}\n"; 524 | exit 1; 525 | } else { 526 | if ( ! $NOOK ) { show_ok_box(); print "This distro version is supported by apache2buddy.pl.\n" } 527 | } 528 | } elsif (exists($ool{$distro})) { 529 | if ( $VERBOSE ) { print "VERBOSE -> Oracle Linux Version: ". $version . "\n"} 530 | my @oracle_linux_version = split('\.', $version); 531 | if ( $VERBOSE ) { 532 | foreach my $item (@oracle_linux_version) { 533 | print "VERBOSE: ". $item . "\n"; 534 | } 535 | } 536 | my $major_oracle_linux_version = $oracle_linux_version[0]; 537 | if ( $VERBOSE ) { print "VERBOSE -> Major Oracle Linux Version Detected ". $major_oracle_linux_version . "\n"} 538 | if ($major_oracle_linux_version lt 8 ) { 539 | show_crit_box(); print "${RED}ERROR: This distro version (${CYAN}$version${ENDC}${RED}) is not supported by apache2buddy.pl.${ENDC}\n"; 540 | exit 1; 541 | } else { 542 | if ( ! $NOOK ) { show_ok_box(); print "This distro version is supported by apache2buddy.pl.\n" } 543 | } 544 | } 545 | 546 | } elsif ($distro eq "Gentoo") { 547 | show_crit_box(); print "${RED}ERROR: Gentoo is a reasonable endeavour, and is not \"officially\" supported by apache2buddy.pl.${ENDC}\n"; 548 | show_advisory_box(); print "To run anyway (at your own risk), try -O or --skip-os-version-check.\n"; 549 | exit 1; 550 | } else { 551 | show_crit_box(); print "${RED}ERROR: This distro is not supported by apache2buddy.pl.${ENDC}\n"; 552 | # list supported OS distros 553 | if ( ! $NOINFO ) { show_advisory_box(); print "${YELLOW}Supported Distro's:${ENDC} '${CYAN}" . join("${ENDC}', '${CYAN}", @supported_os_list) . "${ENDC}'. To run anyway (at your own risk), try -O or --skip-os-version-check.\n"} 554 | exit 1; 555 | } 556 | } 557 | 558 | sub systemcheck_large_logs { 559 | my ($logdir) = @_; 560 | if ( -d $logdir ) { 561 | my @logs; 562 | my $logfiles_raw = find(sub {push @logs, $File::Find::name if -s >= 1024000000;}, $logdir); 563 | foreach my $log (@logs) { 564 | chomp($log); 565 | # Issue 255 skip reporting already gzipped logs, as they have already been rotated 566 | if ( ! $log =~ m/\.gz$/ ) { 567 | my $size = -s $log; 568 | my $humansize = sprintf "%.2f", $size/1024/1024/1024; 569 | show_crit_box(); print $log . " --> " . $humansize . "GB\b\n"; 570 | } 571 | } 572 | if (@logs == 0) { 573 | if ( ! $NOOK ) { show_ok_box(); print "${GREEN}No large log files were found in ${CYAN}$logdir${ENDC}.\n"; } 574 | } else { 575 | show_advisory_box(); print "${YELLOW}Consider setting up a log rotation policy.${ENDC}\n"; 576 | show_advisory_box(); print "${YELLOW}Note: Log rotation should already be set up under normal circumstances, so very${ENDC}\n"; 577 | show_advisory_box(); print "${YELLOW}Large error logs can indicate a fundmental issue with the website / web application.${ENDC}\n"; 578 | } 579 | } 580 | # silently proceed if the folder doesnt exist 581 | } 582 | 583 | sub files_in_array_that_exist_and_are_readable { 584 | # takes an array of filenames as argument, filters out files that may cause an exception later on. 585 | # see issue #347 586 | my @in_array = @_; 587 | # Lets programatically rip through the array and work out which 588 | # ones exist and put those in a new array. 589 | my @out_array; 590 | foreach my $file(@in_array) { 591 | chomp($file); 592 | # if the file exists, and we have permission to read it ( and we should, we are root after all if we got this far) 593 | if ( -f $file && -r $file) { 594 | push(@out_array,$file) 595 | } 596 | } 597 | return @out_array; 598 | } 599 | 600 | 601 | # here we're going to build a list of the files included by the Apache 602 | # configuration 603 | sub build_list_of_files { 604 | my ($base_apache_config,$apache_root) = @_; 605 | 606 | # these to arrays will contain lists of Apache configuration files 607 | # this is going to be the ultimate list of files that will be parsed 608 | # searching for arguments 609 | my @master_list; 610 | # this will be a "scratch" space to store a list of files that 611 | # currently need to be searched for more "include" lines 612 | my @find_includes_in; 613 | 614 | # put the main configuration file into the list of files we're going 615 | # to include 616 | push(@master_list,$base_apache_config); 617 | 618 | # put the main configuration file into the list of files we need to 619 | # search for include lines 620 | push(@find_includes_in,$base_apache_config); 621 | 622 | #get the Include lines from the main apache config 623 | @master_list = find_included_files(\@master_list,\@find_includes_in,$apache_root); 624 | } 625 | 626 | # here we're going to build an array holding the content of all of the 627 | # available configuration files 628 | sub build_config_array { 629 | my ($base_apache_config,$apache_root) = @_; 630 | 631 | # these to arrays will contain lists of Apache configuration files 632 | # this is going to be the ultimate list of files that will be parsed 633 | # searching for arguments 634 | my @master_list; 635 | 636 | # this will be a "scratch" space to store a list of files that 637 | # currently need to be searched for more "include" lines 638 | my @find_includes_in; 639 | 640 | # put the main configuration file into the list of files we're going 641 | # to include 642 | push(@master_list,$base_apache_config); 643 | 644 | # put the main configuration file into the list of files we need to 645 | # search for include lines 646 | push(@find_includes_in,$base_apache_config); 647 | 648 | #get the Include lines from the main apache config 649 | @master_list = find_included_files(\@master_list,\@find_includes_in,$apache_root); 650 | } 651 | 652 | # this will find all of the files that need to be included 653 | sub find_included_files { 654 | my ($master_list, $find_includes_in, $apache_root) = @_; 655 | 656 | # get the number of elements in the array 657 | my $count = @$find_includes_in; 658 | 659 | # this array will eventually hold the entire apache configuration 660 | my @master_config_array; 661 | 662 | # while there are still entries in this array, keep processing 663 | while ( $count > 0 ) { 664 | my $file = $$find_includes_in[0]; 665 | 666 | print "VERBOSE: Processing ".$file."\n" if $main::VERBOSE; 667 | 668 | if(-d $file && $file !~ /\*$/) { 669 | print "VERBOSE: Adding glob to ".$file.", is a directory\n" if $main::VERBOSE; 670 | $file .= "/" if($file !~ /\/$/); 671 | $file .= "*"; 672 | } 673 | 674 | # open the file 675 | open(FILE,$file) || die("Unable to open file: ".$file."\n"); 676 | 677 | # push the file into an array 678 | my @file = ; 679 | 680 | # put the file in the master configuration array 681 | push(@master_config_array,@file); 682 | 683 | # close the file 684 | close(FILE); 685 | 686 | # search the file for includes 687 | foreach (@file) { 688 | 689 | # this will be used to store a list of any new include 690 | # lines found 691 | # my @new_includes; 692 | 693 | # if the line looks like an include, then we want to examine it 694 | if ( $_ =~ m/^\s*include\s+/i ) { 695 | # grab the included file name or file glob 696 | $_ =~ s/\s*include\s+(.+)\s*/$1/i; 697 | 698 | # strip out any quoting 699 | $_ =~ s/['"]+//g; 700 | 701 | # prepend the Apache root for files or 702 | # globs that are relative 703 | if ( $_ !~ m/^\// ) { 704 | $_ = $apache_root."/".$_; 705 | } 706 | 707 | # check for file globbing 708 | if(-d $_ && $_ !~ /\*$/) { 709 | print "VERBOSE: Adding glob to ".$_.", is a directory\n" if $main::VERBOSE; 710 | $_ .= "/" if($_ !~ /\/$/); 711 | $_ .= "*"; 712 | } 713 | 714 | if ( $_ =~ m/.*\*.*/ ) { 715 | my $glob = $_; 716 | my @include_files; 717 | chomp($glob); 718 | 719 | # if the include is a file glob, 720 | # expand it and add the files 721 | # to the list 722 | my @new_includes = expand_included_files(\@include_files, $glob, $apache_root); 723 | my @sane_includes = files_in_array_that_exist_and_are_readable(@new_includes); 724 | push(@$master_list,@sane_includes); 725 | push(@$find_includes_in,@sane_includes); 726 | } 727 | else { 728 | # if it is not a glob, push the 729 | # line into the configuration 730 | # array 731 | push(@$master_list,$_); 732 | @$master_list = files_in_array_that_exist_and_are_readable(@$master_list); 733 | push(@$find_includes_in,$_); 734 | @$find_includes_in = files_in_array_that_exist_and_are_readable(@$find_includes_in); 735 | } 736 | } 737 | # This extra bit of code is required for apache 2.4's new directive "IncludeOptional" 738 | if ( $_ =~ m/^\s*includeoptional\s+/i ) { 739 | # grab the included file name or file glob 740 | $_ =~ s/\s*includeoptional\s+(.+)\s*/$1/i; 741 | 742 | # strip out any quoting 743 | $_ =~ s/['"]+//g; 744 | 745 | # prepend the Apache root for files or 746 | # globs that are relative 747 | if ( $_ !~ m/^\// ) { 748 | $_ = $apache_root."/".$_; 749 | } 750 | 751 | # check for file globbing 752 | if(-d $_ && $_ !~ /\*$/) { 753 | print "VERBOSE: Adding glob to ".$_.", is a directory\n" if $main::VERBOSE; 754 | $_ .= "/" if($_ !~ /\/$/); 755 | $_ .= "*"; 756 | } 757 | 758 | if ( $_ =~ m/.*\*.*/ ) { 759 | my $glob = $_; 760 | my @include_files; 761 | chomp($glob); 762 | 763 | # if the include is a file glob, 764 | # expand it and add the files 765 | # to the list 766 | my @new_includes = expand_included_files(\@include_files, $glob, $apache_root); 767 | my @sane_includes = files_in_array_that_exist_and_are_readable(@new_includes); 768 | push(@$master_list,@sane_includes); 769 | push(@$find_includes_in,@sane_includes); 770 | } 771 | else { 772 | # if it is not a glob, push the 773 | # line into the configuration 774 | # array 775 | push(@$master_list,$_); 776 | @$master_list = files_in_array_that_exist_and_are_readable(@$master_list); 777 | push(@$find_includes_in,$_); 778 | @$find_includes_in = files_in_array_that_exist_and_are_readable(@$find_includes_in); 779 | } 780 | } 781 | } 782 | # trim the first entry off the array now that we have 783 | # processed it 784 | shift(@$find_includes_in); 785 | 786 | # get the new count of files left to look at 787 | $count = @$find_includes_in; 788 | } 789 | 790 | # return the config array with the included files attached 791 | return @master_config_array; 792 | } 793 | 794 | # this will expand a glob into a list of individual files 795 | sub expand_included_files { 796 | my ($include_files, $glob, $apache_root) = @_; 797 | 798 | # use a call to ls to get a list of the files from the glob 799 | my @files = `ls $glob 2> /dev/null`; 800 | 801 | # add the files from the glob to the array we're going to pass back 802 | foreach(@files) { 803 | chomp($_); 804 | if ( -f $_ ) { 805 | push(@$include_files,$_); 806 | print "VERBOSE: Adding ".$_." to list of files for processing\n" if $main::VERBOSE; 807 | } else { 808 | print "VERBOSE: Skipping ".$_." as it is a directory\n" if $main::VERBOSE; 809 | } 810 | } 811 | 812 | # return the include_files array with the files from the glob attached 813 | return @$include_files; 814 | } 815 | 816 | # search the configuration array for a defined value that is not inside of a 817 | # virtual host 818 | sub find_master_value { 819 | my ($config_array, $model, $config_element) = @_; 820 | 821 | # store our results in an array 822 | my @results; 823 | 824 | # used to control whether or not we are currently ignoring elements 825 | # while searching the array 826 | my $ignore = 0; 827 | 828 | my $ignore_by_model = 0; 829 | my $ifmodule_count = 0; 830 | 831 | # apache has four available models - prefork, worker, event, and itk. only one can be 832 | # in use at a time. we have already determined which model is being used. We also only 833 | # support PreFork, any any one time three MPM's will need to be ignored. 834 | my $ignore_model1; 835 | my $ignore_model2; 836 | my $ignore_model3; # always ignore MPM ITK 837 | 838 | if ( $model =~ m/.*worker.*/i ) { 839 | $ignore_model1 = "prefork"; 840 | $ignore_model2 = "event"; 841 | $ignore_model3 = "itk"; 842 | } elsif ( $model =~ m/.*event.*/i ) { 843 | $ignore_model1 = "worker"; 844 | $ignore_model2 = "prefork"; 845 | $ignore_model3 = "itk"; 846 | } else { 847 | # default to prefork 848 | $ignore_model1 = "worker"; 849 | $ignore_model2 = "event"; 850 | $ignore_model3 = "itk"; 851 | } 852 | 853 | print "VERBOSE: Searching Apache configuration for the ".$config_element." directive\n" if $main::VERBOSE; 854 | 855 | # search for the string in the configuration array 856 | foreach (@$config_array) { 857 | # ignore lines that are comments 858 | if ( $_ !~ m/^\s*#/ ) { 859 | chomp($_); 860 | 861 | # we ignore lines that are within a Directory, Location, 862 | # File, or Virtualhost block 863 | 864 | # check to see if we have an opening tag for one of the 865 | # block types listed above 866 | if ( $_ =~ m/^\s*<(directory|location|files|virtualhost|ifmodule\s.*$ignore_model1|ifmodule\s.*$ignore_model2|ifmodule\s.*$ignore_model3)/i ) { 867 | #print "Starting to ignore lines: ".$_."\n"; 868 | $ignore = 1; 869 | } 870 | # check for a closing block to stop ignoring lines 871 | if ( $_ =~ m/^\s*<\/(directory|location|files|virtualhost|ifmodule)/i ) { 872 | #print "Starting to watch lines: ".$_."\n"; 873 | $ignore = 0; 874 | } 875 | 876 | # if we're not ignoring lines, check and see if we've 877 | # found the configuration element we're looking for 878 | if ( $ignore != 1 ) { 879 | # if we find a match 880 | if ( $_ =~ m/^\s*$config_element\s+.*/i ) { 881 | chomp($_); 882 | $_ =~ s/^\s*$config_element\s+(.*)/$1/i; 883 | $_ =~ s/\r//g; 884 | push(@results,$_); 885 | } 886 | } 887 | } 888 | } 889 | 890 | # if we find multiple definitions for the same element, we should 891 | # return the last one 892 | my $result; 893 | 894 | if ( @results > 1 ) { 895 | $result = $results[@results - 1]; 896 | } 897 | else { 898 | $result = $results[0]; 899 | } 900 | 901 | #Result not found 902 | if (@results == 0) { 903 | $result = "CONFIG NOT FOUND"; 904 | } 905 | 906 | print "VERBOSE: $result \n" if $main::VERBOSE; 907 | # Ubuntu does not store the Apache user, group, or pidfile definitions 908 | # in the apache2.conf file. instead, variables are in the configuration 909 | # file and the real values are in /etc/apache2/envvars. this is a 910 | # workaround for that behavior. 911 | if ( $config_element =~ m/[users|group|pidfile]/i && $result =~ m/^\$/i ) { 912 | if ( -e "/etc/debian_version" && -e "/etc/apache2/envvars") { 913 | print "VERBOSE: Using Ubuntu workaround for: ".$config_element."\n" if $main::VERBOSE; 914 | print "VERBOSE: Processing /etc/apache2/envvars\n" if $main::VERBOSE; 915 | 916 | open(ENVVARS,"/etc/apache2/envvars") || die "Could not open file: /etc/apache2/envvars\n"; 917 | my @envvars = ; 918 | close(ENVVARS); 919 | 920 | # change "pidfile" to match Ubuntu's "pid_file" 921 | # definition 922 | if ( $config_element =~ m/pidfile/i ) { 923 | $config_element = "pid_file"; 924 | } 925 | 926 | foreach (@envvars) { 927 | if ( $_ =~ m/.*$config_element.*/i ) { 928 | chomp($_); 929 | $_ =~ s/^.*=(.*)\s*$/$1/i; 930 | $result = $_; 931 | } 932 | } 933 | } 934 | } 935 | 936 | # return the value to the main program 937 | return $result; 938 | } 939 | 940 | # this will examine the memory usage of the apache processes and return one of 941 | # three different outputs: average usage across all processes, the memory usage 942 | # by the largest process, or the memory usage by the smallest process 943 | sub get_memory_usage { 944 | my ($process_name, $apache_user_running, $search_type) = @_; 945 | 946 | my (@proc_mem_usages, $result); 947 | 948 | print "VERBOSE: Get '".$search_type."' memory usage\n" if $main::VERBOSE; 949 | 950 | # get a list of the pid's for apache running as the appropriate user 951 | my @pids = `ps aux | grep $process_name | grep "^$apache_user_running" | awk \'{ print \$2 }\'`; 952 | print "VERBOSE: List pids:\n @pids" if $main::VERBOSE; 953 | 954 | # if length of @pids is still zero then die with an error. 955 | if (@pids == 0) { 956 | show_crit_box(); print ("Error getting a list of PIDs\n"); 957 | print ("DEBUG -> Process Name: ".$process_name."\nDEBUG -> Apache_user: ".$apache_user_running."\nDEBUG -> Search Type: ".$search_type."\n\n"); 958 | exit 1; 959 | } 960 | # figure out how much memory each process is using 961 | foreach my $pid (@pids) { 962 | chomp($pid); 963 | # pmap -d is used to determine the memory usage for the 964 | # individual processes 965 | my ($distro, $version, $codename) = get_os_platform(); 966 | #output of 'pmap' is different depending on distro! 967 | my $pid_mem_usage; 968 | if (ucfirst($distro) eq "SUSE Linux Enterprise Server" ) { 969 | $pid_mem_usage = `LANGUAGE=en_GB.UTF-8 pmap -d $pid | egrep "writable-private" | awk \'{ print \$1 }\'`; 970 | } else { 971 | $pid_mem_usage = `LANGUAGE=en_GB.UTF-8 pmap -d $pid | egrep "writeable/private" | awk \'{ print \$4 }\'`; 972 | } 973 | $pid_mem_usage =~ s/K//; 974 | chomp($pid_mem_usage); 975 | 976 | print "VERBOSE: Memory usage by PID ".$pid." is ".$pid_mem_usage."K\n" if $main::VERBOSE; 977 | 978 | # on a busy system, the grep output will return the pid for the 979 | # grep process itself, which will be gone by the time we get 980 | # around to running pmap 981 | if ( $pid_mem_usage ne "" ) { 982 | push(@proc_mem_usages, $pid_mem_usage); 983 | } 984 | } 985 | 986 | # examine the array 987 | if ( $search_type eq "high" ) { 988 | # to find the largest process, sort the values from largest to 989 | # smallest and take the first one 990 | @proc_mem_usages = sort { $b <=> $a } @proc_mem_usages; 991 | $result = $proc_mem_usages[0] / 1024; 992 | } 993 | if ( $search_type eq "low" ) { 994 | # to find the smallest process, sort the values from smallest to 995 | # largest and take the first one 996 | @proc_mem_usages = sort { $a <=> $b } @proc_mem_usages; 997 | $result = $proc_mem_usages[0] / 1024; 998 | } 999 | if ( $search_type eq "average" ) { 1000 | # to get the average, add up the total amount of memory used by 1001 | # each process, and then divide by the number of processes 1002 | my $sum = 0; 1003 | my $count; 1004 | foreach (@proc_mem_usages) { 1005 | $sum = $sum + $_; 1006 | $count++; 1007 | } 1008 | 1009 | # our result is in kilobytes, convert it to megabytes before 1010 | # returning it 1011 | $result = $sum / $count / 1024; 1012 | } 1013 | 1014 | # round off the result 1015 | $result = round($result); 1016 | 1017 | return $result; 1018 | } 1019 | 1020 | # this function accepts the path to a file and then tests to see whether the 1021 | # item at that path is an Apache binary 1022 | sub test_process { 1023 | my ($process_name) = @_; 1024 | # THIS SUBROUTINE WORKS FINE WITH httpd4u EXECUTABLES TOO. 1025 | # Reduce to only aphanumerics, to deal with "nginx: master process" or any newlnes 1026 | $process_name = `echo -n $process_name | sed 's/://g'`; 1027 | 1028 | # the first line of output from "httpd -V" should tell us whether or 1029 | # not this is Apache 1030 | our @output; 1031 | if ( $process_name eq '/usr/sbin/httpd' ) { 1032 | @output = `LANGUAGE=en_GB.UTF-8 $process_name -V 2>&1 | grep "Server version"`; 1033 | print "VERBOSE: First line of output from \"$process_name -V\": $output[0]\n" if $main::VERBOSE; 1034 | } elsif ( $process_name eq '/usr/sbin/httpd.worker' ) { 1035 | # Handle Worker processes better 1036 | # BUGFIX, first identified by C. Piper Balta 1037 | @output = `LANGUAGE=en_GB.UTF-8 $process_name -V 2>&1 | grep "Server version"`; 1038 | print "VERBOSE: First line of output from \"$process_name -V\": $output[0]\n" if $main::VERBOSE; 1039 | } elsif ( $process_name eq '/usr/sbin/apache2' ) { 1040 | @output = `LANGUAGE=en_GB.UTF-8 /usr/sbin/apache2ctl -V 2>&1 | grep "Server version"`; 1041 | print "VERBOSE: First line of output from \"/usr/sbin/apache2ctl -V\": $output[0]\n" if $main::VERBOSE; 1042 | } elsif ( $process_name eq '/usr/sbin/httpd-prefork' ) { 1043 | @output = `LANGUAGE=en_GB.UTF-8 /usr/sbin/apache2ctl -V 2>&1 | grep "Server version"`; 1044 | print "VERBOSE: First line of output from \"/usr/sbin/apache2ctl -V\": $output[0]\n" if $main::VERBOSE; 1045 | } elsif ( $process_name eq '/usr/local/apache/bin/httpd' ) { 1046 | if ( ! $NOWARN ) { show_warn_box(); print "${RED}Apache seems to have been installed from source, its technically unsupported, we may get errors${ENDC}\n" } 1047 | @output = `LANGUAGE=en_GB.UTF-8 $process_name -V 2>&1 | grep "Server version"`; 1048 | print "VERBOSE: First line of output from \"/usr/local/apache/bin/httpd -V\": $output[0]\n" if $main::VERBOSE; 1049 | } elsif ( $process_name eq '/opt/apache2/bin/httpd' ) { 1050 | if ( ! $NOWARN ) { show_warn_box(); print "${RED}Apache seems to have been installed from a self build package, its technically unsupported, we may get errors${ENDC}\n" } 1051 | @output = `LANGUAGE=en_GB.UTF-8 $process_name -V 2>&1 | grep "Server version"`; 1052 | print "VERBOSE: First line of output from \"/opt/apache2/bin/httpd -V\": $output[0]\n" if $main::VERBOSE; 1053 | } else { 1054 | # this catchall should cover all other possibilities, such as 1055 | # nginx, varnish, etc. 1056 | # BUGFIX, first identified by C. Piper Balta. 1057 | my $return_val = 0; 1058 | return $return_val; 1059 | } 1060 | 1061 | 1062 | my $return_val = 0; 1063 | #if ( $output eq '' ) { 1064 | # $return_val = 0; 1065 | #} 1066 | 1067 | # check for valid variable 1068 | if ( ! $output[0] ) { 1069 | show_crit_box(); 1070 | print "${RED}Something went wrong, and I suspect you have a syntax error in your apache configuration.${ENDC}\n"; 1071 | show_crit_box(); 1072 | print "${YELLOW}See \"${CYAN}systemctl status httpd.service${ENDC}\" ${YELLOW}and \"${CYAN}journalctl -xe${ENDC}\" ${YELLOW}for details.${ENDC}\n"; 1073 | exit 1; 1074 | } else { 1075 | # check for output matching Apache' 1076 | if ( $output[0] =~ m/^Server version.*Apache\/[0-9].*/ ) { 1077 | $return_val = 1; 1078 | } 1079 | elsif ( $output[0] =~ m/^Server version.*Server\/[0-9].*/ ) { 1080 | print "${YELLOW}Apache server was build with version string \"${CYAN}Server version: Server/....${YELLOW}\" and not as usual \"${CYAN}Server version: Apache/....${YELLOW}\"${ENDC}\n"; 1081 | $return_val = 1; 1082 | } 1083 | } 1084 | return $return_val; 1085 | } 1086 | 1087 | # this will return the pid for the process listening on the port specified 1088 | sub get_pid { 1089 | my ( $port ) = @_; 1090 | my @pids; 1091 | # find the pid for the software listening on the specified port. this 1092 | # might return multiple values depending on Apache's listen directives 1093 | if ($ss_path) { 1094 | @pids = `LANGUAGE=en_GB.UTF-8 $ss_path -ntlp | awk '/:$port / && /LISTEN/ { print \$6 }' | cut -d= -f2 | cut -d, -f1`; 1095 | } elsif ($netstat_path) { 1096 | @pids = `LANGUAGE=en_GB.UTF-8 $netstat_path -ntap | egrep "LISTEN" | grep \":$port \" | awk \'{ print \$7 }\' | cut -d / -f 1`; 1097 | } 1098 | print "VERBOSE: ".@pids." found listening on port $port\n" if $main::VERBOSE; 1099 | 1100 | # set an initial, invalid PID. 1101 | my $pid = 0;; 1102 | foreach (@pids) { 1103 | chomp($_); 1104 | $_ =~ s/(.*)\/.*/$1/; 1105 | if ( $pid == 0 ) { 1106 | $pid = $_; 1107 | } 1108 | elsif ( $pid != $_ ) { 1109 | print "There are multiple PIDs listening on port $port."; 1110 | exit 1; 1111 | } 1112 | else { 1113 | $pid = $_; 1114 | } 1115 | } 1116 | 1117 | # return the pid, or 0 if there is no process found 1118 | if ( $pid eq '' ) { 1119 | $pid = 0; 1120 | } 1121 | 1122 | print "VERBOSE: Returning a PID of ".$pid."\n" if $main::VERBOSE; 1123 | 1124 | return $pid; 1125 | } 1126 | 1127 | # this will return the path to the application running with the specified pid 1128 | sub get_process_name { 1129 | my ( $pid ) = @_; 1130 | 1131 | print "VERBOSE: Finding process running with a PID of ".$pid."\n" if $main::VERBOSE; 1132 | 1133 | # based on the process name, we can figure out where the binary lives 1134 | my $process_name = `ps ax | grep "\^[[:space:]]*$pid\[[:space:]]" | awk \'{print \$5 }\'`; 1135 | chomp($process_name); 1136 | 1137 | print "VERBOSE: Found process ".$process_name."\n" if $main::VERBOSE; 1138 | 1139 | # return the process name, or 0 if there is no name found 1140 | if ( $process_name eq '' ) { 1141 | $process_name = 0; 1142 | } 1143 | 1144 | return $process_name; 1145 | } 1146 | 1147 | # this will return the apache root directory when given the full path to an 1148 | # Apache binary 1149 | sub get_apache_root { 1150 | our ( $process_name ) = @_; 1151 | our $apache_root; 1152 | # use the identified Apache binary to figure out where the root directory is 1153 | # for the Apache instance 1154 | if ( $process_name eq "/usr/sbin/apache2") { 1155 | # use apache2ctl instead ... 1156 | $apache_root = `apache2ctl -V 2>&1 | grep \"HTTPD_ROOT\"`; 1157 | $apache_root =~ s/.*=\"(.*)\"/$1/; 1158 | chomp($apache_root); 1159 | } else { 1160 | $apache_root = `$process_name -V 2>&1 | grep \"HTTPD_ROOT\"`; 1161 | $apache_root =~ s/.*=\"(.*)\"/$1/; 1162 | chomp($apache_root); 1163 | } 1164 | if ( $apache_root eq '' ) { 1165 | $apache_root = 0; 1166 | } 1167 | 1168 | return $apache_root; 1169 | } 1170 | 1171 | # this will return the apache configuration file, relative to the apache root 1172 | # for the provided apache binary 1173 | sub get_apache_conf_file { 1174 | our $apache_conf_file; 1175 | my ( $process_name ) = @_; 1176 | if ( $process_name eq "/usr/sbin/apache2") { 1177 | # use apache2ctl instead ... 1178 | $apache_conf_file = `apache2ctl -V 2>&1 | grep \"SERVER_CONFIG_FILE\"`; 1179 | $apache_conf_file =~ s/.*=\"(.*)\"/$1/; 1180 | chomp($apache_conf_file); 1181 | } else { 1182 | $apache_conf_file = `$process_name -V 2>&1 | grep \"SERVER_CONFIG_FILE\"`; 1183 | $apache_conf_file =~ s/.*=\"(.*)\"/$1/; 1184 | chomp($apache_conf_file); 1185 | } 1186 | # return the apache configuration file, or 0 if there is no result 1187 | if ( $apache_conf_file eq '' ) { 1188 | $apache_conf_file = 0; 1189 | } 1190 | 1191 | return $apache_conf_file; 1192 | } 1193 | 1194 | # this will return the apache default pid file 1195 | sub get_apache_pid_file { 1196 | our $apache_pid_file; 1197 | my ( $process_name ) = @_; 1198 | if ( $process_name eq "/usr/sbin/apache2") { 1199 | # use apache2ctl instead ... 1200 | $apache_pid_file = `apache2ctl -V 2>&1 | grep \"DEFAULT_PIDLOG\"`; 1201 | $apache_pid_file =~ s/.*=\"(.*)\"/$1/; 1202 | chomp($apache_pid_file); 1203 | } else { 1204 | $apache_pid_file = `$process_name -V 2>&1 | grep \"DEFAULT_PIDLOG\"`; 1205 | $apache_pid_file =~ s/.*=\"(.*)\"/$1/; 1206 | chomp($apache_pid_file); 1207 | } 1208 | # return the apache configuration file, or 0 if there is no result 1209 | if ( $apache_pid_file eq '' ) { 1210 | $apache_pid_file = 0; 1211 | } 1212 | 1213 | return $apache_pid_file; 1214 | } 1215 | 1216 | sub itk_detect { 1217 | my ($model) = @_; 1218 | if ( $model =~ /(.*)itk(.*)/) { 1219 | show_crit_box(); print "MPM ITK was detected, apache2buddy.pl does odd things so we quit. Sorry.\n"; 1220 | show_advisory_box(); print "MPM ITK is not supported. Unload the module and try again.\n\n"; 1221 | exit 1; 1222 | } 1223 | } 1224 | 1225 | # this will determine whether this apache is using the worker or the prefork 1226 | # model based on the way the binary was built 1227 | sub get_apache_model { 1228 | our $model; 1229 | my ( $process_name ) = @_; 1230 | if ( $process_name =~ m{^\Q/usr/bin/apache2\E} ) { 1231 | # In apache2, worker / prefork / event are no longer compiled-in. 1232 | # Instead, with is a loaded in module 1233 | # differing from httpd / httpd24u's process directly, in ubuntu we need to run apache2ctl. 1234 | if ($VERBOSE) { print "VERBOSE: Looking for model, first trying 'apache2ctl'.\n" } 1235 | $model = `apache2ctl -M 2>&1 | egrep "worker|prefork|event|itk"`; 1236 | # see issue #334, apachectl was our fall back but now we need to also fall back to apache2. 1237 | my @array_models = ('worker','prefork','event','itk'); 1238 | if (!(grep $model, @array_models)) { 1239 | if ($VERBOSE) { print "VERBOSE: model not found, falling back to 'apache2', last try...\n" } 1240 | $model = `apache2 -M 2>&1 | egrep "worker|prefork|event|itk"`; 1241 | } 1242 | # if we detect itk module, we need to stop immediately: 1243 | if ($VERBOSE) { print "VERBOSE: $model" } 1244 | if ($VERBOSE) { print "VERBOSE: ITK DETECTTOR STARTED\n" } 1245 | itk_detect($model); 1246 | if ($VERBOSE) { print "VERBOSE: ITK DETECTTOR PASSED\n" } 1247 | if ($VERBOSE) { print "VERBOSE: $model" } 1248 | chomp($model); 1249 | if ($VERBOSE) { print "VERBOSE: $model\n" } 1250 | if ($VERBOSE) { print "VERBOSE: REGEX Filter started.\n" } 1251 | $model =~ s/\s*mpm_(.*)_module\s*\S*/$1/; 1252 | if ($VERBOSE) { print "VERBOSE: REGEX Filter finished.\n" } 1253 | if ($VERBOSE) { print "VERBOSE: $model\n" } 1254 | if ($VERBOSE) { print "VERBOSE: Return Value: $model\n" } 1255 | return $model; 1256 | } else { 1257 | if ($VERBOSE) { print "VERBOSE: Looking for model, first trying 'apachectl'.\n" } 1258 | $model = `apachectl -M 2>&1 | egrep "worker|prefork|event|itk"`; 1259 | # Gotcha in Fedora 32 - so likely to appear in later versions of apache (circa 2.4.43) 1260 | # see issue #334, apachectl was our fall back but now we need to also fall back to httpd. 1261 | my @array_models = ('worker','prefork','event','itk'); 1262 | if (!(grep $model, @array_models)) { 1263 | if ($VERBOSE) { print "VERBOSE: model not found, falling back to 'httpd', last try...\n" } 1264 | $model = `httpd -M 2>&1 | egrep "worker|prefork|event|itk"`; 1265 | } 1266 | our $model; 1267 | if ($VERBOSE) { print "VERBOSE: $model" } 1268 | if ($VERBOSE) { print "VERBOSE: ITK DETECTOR STARTED\n" } 1269 | itk_detect($model); 1270 | if ($VERBOSE) { print "VERBOSE: ITK DETECTOR PASSED\n" } 1271 | chomp($model); 1272 | if ($VERBOSE) { print "VERBOSE: $model\n" } 1273 | if ($VERBOSE) { print "VERBOSE: REGEX Filter started.\n" } 1274 | $model =~ s/\s*mpm_(.*)_module\s*\S*/$1/; 1275 | if ($VERBOSE) { print "VERBOSE: REGEX Filter finished.\n" } 1276 | if ($VERBOSE) { print "VERBOSE: $model\n" } 1277 | if ($VERBOSE) { print "VERBOSE: Return Value: $model\n" } 1278 | return $model; 1279 | } 1280 | } 1281 | 1282 | # this will get the Apache version string 1283 | sub get_apache_version { 1284 | my ( $process_name ) = @_; 1285 | # set a default value 1286 | our $version = 0; 1287 | 1288 | # check for red hat style spache installs 1289 | if ( $process_name eq '/usr/sbin/httpd' || $process_name eq '/usr/sbin/httpd.worker' ) { 1290 | $version = `LANGUAGE=en_GB.UTF-8 $process_name -V 2>&1 | grep "Server version"`; 1291 | chomp($version); 1292 | $version =~ s/.*:\s(.*)$/$1/; 1293 | } elsif ( $process_name eq '/usr/sbin/apache2' ) { 1294 | # ubuntu has to be different, so... 1295 | $version = `LANGUAGE=en_GB.UTF-8 /usr/sbin/apache2ctl -V 2>&1 | grep "Server version"`; 1296 | chomp($version); 1297 | $version =~ s/.*:\s(.*)$/$1/; 1298 | } else { 1299 | # check for compiled from source versions 1300 | $version = `LANGUAGE=en_GB.UTF-8 $process_name -V 2>&1 | grep "Server version"`; 1301 | chomp($version); 1302 | $version =~ s/.*:\s(.*)$/$1/; 1303 | } 1304 | 1305 | return $version; 1306 | } 1307 | 1308 | # this will us ps to determine the Apache uptime. it returns an array 1309 | sub get_apache_uptime { 1310 | my ( $pid ) = @_; 1311 | 1312 | # this will return the running time for the given pid in the format 1313 | # "days-hours:minutes:seconds" 1314 | my $uptime = `ps -eo \"\%p \%t\" | grep \"^[[:space:]]*$pid \" | awk \'{ print \$2 }\'`; 1315 | chomp($uptime); 1316 | 1317 | print "VERBOSE: PID passed to uptime function: $pid\n" if $main::VERBOSE; 1318 | print "VERBOSE: Raw uptime: $uptime\n" if $main::VERBOSE; 1319 | 1320 | # check to see if we've been running for multiple days 1321 | my ($days, $hours, $minutes, $seconds); 1322 | if ( $uptime =~ m/^.*-.*:.*:.*$/ ) { 1323 | $days = $uptime; 1324 | $days =~ s/([0-9]*)-.*/$1/; 1325 | 1326 | # trim the days off of our uptime value 1327 | $uptime =~ s/.*-(.*)/$1/; 1328 | 1329 | ($hours, $minutes, $seconds) = split(':', $uptime); 1330 | } 1331 | elsif ( $uptime =~ m/^.*:.*:.*/ ) { 1332 | $days = 0; 1333 | ($hours, $minutes, $seconds) = split(':', $uptime); 1334 | } 1335 | elsif ( $uptime =~ m/^.*:.*/) { 1336 | $days = 0; 1337 | $hours = 0; 1338 | ($minutes, $seconds) = split(':', $uptime); 1339 | } 1340 | else { 1341 | $days = 0; 1342 | $hours = 0; 1343 | $minutes = 00; 1344 | $seconds = 00; 1345 | } 1346 | 1347 | # push everything into an array to pass back 1348 | my @apache_uptime = ( $days, $hours, $minutes, $seconds ); 1349 | 1350 | 1351 | 1352 | return @apache_uptime; 1353 | } 1354 | 1355 | # return the global value for a PHP setting 1356 | sub get_php_setting { 1357 | my ( $php_bin, $element ) = @_; 1358 | 1359 | # this will return an array with all of the local and global PHP 1360 | # settings 1361 | 1362 | # code to address bug raised in issue #197 (cli memory limits on debian / ubuntu) 1363 | # sanity check if we are using cli or apache 1364 | my $config = `$php_bin -r 'phpinfo(1);' | grep -i config | grep -i loaded`; 1365 | chomp ($config); 1366 | if ($VERBOSE) { print "VERBOSE: PHP: $config\n" } 1367 | 1368 | if ( $config =~ /cli/ ) { 1369 | if ($VERBOSE) { print "VERBOSE: PHP: Attempting to find real apache php.ini file...\n" } 1370 | # try to find the apache2 one 1371 | if ( -f "/etc/php5/apache2/php.ini" ) { 1372 | our $real_config = "/etc/php5/apache2/php.ini"; 1373 | } else { 1374 | # looking for /etc/php/*/apache2/php.ini 1375 | my @files = glob "/etc/php/*/apache2/php.ini"; 1376 | 1377 | if (@files != 1) { 1378 | # looking for /etc/php/*/fpm/php.ini 1379 | my @files = glob "/etc/php/*/fpm/php.ini"; 1380 | 1381 | # extra special sauce for Gentoo 1382 | if (@files != 1) { 1383 | my @files = glob "/etc/php/apache2-php*/php.ini"; 1384 | 1385 | # This block should ideally never be hit. 1386 | if (@files != 1) { 1387 | our $real_config = "Not Found."; 1388 | } else { 1389 | our $real_config = @files[0]; 1390 | } 1391 | } else { 1392 | our $real_config = @files[0]; 1393 | } 1394 | } else { 1395 | our $real_config = @files[0]; 1396 | } 1397 | } 1398 | 1399 | our $real_config; 1400 | if ($VERBOSE) { print "VERBOSE: PHP: Real apache php.ini file is $real_config, using that...\n" } 1401 | our @php_config_array = `$php_bin -c $real_config -r 'phpinfo(4);'`; 1402 | } else { 1403 | our @php_config_array = `$php_bin -r 'phpinfo(4);'`; 1404 | } 1405 | 1406 | my @results; 1407 | 1408 | # search the array for our desired setting 1409 | our @php_config_array; 1410 | foreach (@php_config_array) { 1411 | chomp($_); 1412 | if ( $_ =~ m/^\s*$element\s*/ ) { 1413 | chomp($_); 1414 | $_ =~ s/.*=>\s+(.*)\s+=>.*/$1/; 1415 | push(@results, $_); 1416 | } 1417 | } 1418 | 1419 | # if we find multiple definitions for the same element, we should 1420 | # return the last one (just in case) 1421 | my $result; 1422 | 1423 | if ( @results > 1 ) { 1424 | $result = $results[@results - 1]; 1425 | } 1426 | else { 1427 | $result = $results[0]; 1428 | } 1429 | 1430 | # return the value to the main program 1431 | return $result; 1432 | } 1433 | 1434 | sub date { 1435 | # we can use custom date format: date($my_date_format) 1436 | my ($format) = @_; 1437 | # default format is "%Y/%m/%d %H:%M:%S" 1438 | $format ||= "%Y/%m/%d %H:%M:%S"; 1439 | my $current_date = `date +"$format"`; 1440 | $current_date = substr($current_date,0,-1); 1441 | return $current_date; 1442 | } 1443 | 1444 | sub generate_standard_report { 1445 | my ( $available_mem, 1446 | $maxclients, 1447 | $vhost_count, 1448 | $apache_proc_smallest, 1449 | $apache_proc_average, 1450 | $apache_proc_highest, 1451 | $model, 1452 | $uptime, 1453 | $threadsperchild, 1454 | $mysql_memory_usage_mbytes, 1455 | $java_memory_usage_mbytes, 1456 | $redis_memory_usage_mbytes, 1457 | $memcache_memory_usage_mbytes, 1458 | $varnish_memory_usage_mbytes, 1459 | $phpfpm_memory_usage_mbytes, 1460 | $gluster_memory_usage_mbytes) = @_; 1461 | 1462 | 1463 | our @apache_uptime; 1464 | 1465 | # print a report header 1466 | print "\n\n"; 1467 | insert_hrule(); 1468 | print "${BOLD}### GENERAL FINDINGS & RECOMMENDATIONS ###${ENDC}\n"; 1469 | insert_hrule(); 1470 | our $servername; 1471 | our $public_ip_address; 1472 | 1473 | print "Apache2buddy.pl report for server: ${CYAN}$servername${ENDC} \(${CYAN}$public_ip_address${ENDC}\):\n"; 1474 | # show what we're going to use to generate our numbers 1475 | print "\nSettings considered for this report:\n"; # exempt from NOINFO directive. 1476 | if ( ! $NOCHKPID) { 1477 | if ( $apache_uptime[0] == "0" ) { 1478 | if ( ! $NOWARN ) { 1479 | show_crit_box(); print "${RED}*** LOW UPTIME ***${ENDC}.\n"; 1480 | show_advisory_box(); print "${YELLOW}The following recommendations may be misleading - apache has been restarted within the last 24 hours.${ENDC}\n\n"; 1481 | } 1482 | } 1483 | } 1484 | printf ("%-62s ${CYAN}%d %2s${CYAN}\n", "\tYour server's physical RAM:", $available_mem, "MB"); # exempt from NOINFO directive. 1485 | my $memory_remaining = $available_mem - $mysql_memory_usage_mbytes - $java_memory_usage_mbytes - $redis_memory_usage_mbytes - $memcache_memory_usage_mbytes - $varnish_memory_usage_mbytes - $phpfpm_memory_usage_mbytes- $gluster_memory_usage_mbytes; 1486 | printf ("${BOLD}%-62s${ENDC} ${CYAN}%d %2s${ENDC}\n", "\tRemaining Memory after other services considered:", $memory_remaining, "MB"); # exempt from NOINFO directive. 1487 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1488 | printf ("%-62s ${CYAN}%-8d${ENDC} %-30s\n", "\tApache's MaxRequestWorkers directive:", $maxclients, "<--------- Current Setting"); # exempt from NOINFO directive. 1489 | } else { 1490 | printf ("%-62s ${CYAN}%-8d${ENDC} %-30s\n", "\tApache's MaxClients directive:", $maxclients, "<--------- Current Setting"); # exempt from NOINFO directive. 1491 | } 1492 | printf ("%-62s ${CYAN}%s${ENDC}\n", "\tApache MPM Model:", $model); # exempt from NOINFO directive. 1493 | if ( ! $NOINFO ) { printf ("%-62s ${CYAN}%d %2s${ENDC}\n", "\tLargest Apache process (by memory):", $apache_proc_highest, "MB") } 1494 | 1495 | if ($model eq "prefork") { 1496 | # based on the Apache memory usage (size of the largest process, 1497 | # check to see if the maxclients setting for Apache is sane 1498 | our $max_rec_maxclients = $memory_remaining / $apache_proc_highest; 1499 | $max_rec_maxclients = floor($max_rec_maxclients); 1500 | our $tolerance = 0.90; 1501 | our $min_rec_maxclients = floor($max_rec_maxclients*$tolerance); 1502 | 1503 | # determine the maximum potential memory usage by Apache 1504 | my $max_potential_usage = $maxclients * $apache_proc_highest; 1505 | our $max_potential_usage_pct_avail = round(($max_potential_usage/$available_mem)*100); 1506 | our $max_potential_usage_pct_remain = round(($max_potential_usage/$memory_remaining)*100); 1507 | if ( $vhost_count >= $maxclients ) { 1508 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1509 | show_crit_box(); print "\t${RED}Vhost count exceeds MaxRequestWorkers limits!${ENDC}\n"; 1510 | } else { 1511 | show_crit_box(); print "\t${RED}Vhost count exceeds MaxClients limits!${ENDC}\n"; 1512 | } 1513 | } 1514 | if ( $maxclients >= $min_rec_maxclients and $maxclients <= $max_rec_maxclients ) { 1515 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1516 | if ( ! $NOOK ) { show_shortok_box(); print "\t${GREEN}Your MaxRequestWorkers setting is within an acceptable range.${ENDC}\n" } 1517 | } else { 1518 | if ( ! $NOOK ) { show_shortok_box(); print "\t${GREEN}Your MaxClients setting is within an acceptable range.${ENDC}\n" } 1519 | } 1520 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1521 | printf ("${YELLOW}%-75s${ENDC} %-38s\n", "\tYour recommended MaxRequestWorkers setting (based on available memory) is between $min_rec_maxclients and $max_rec_maxclients${ENDC}.", "<-- Acceptable Range (90-100% of Remaining RAM)"); 1522 | if ( $vhost_count >= $max_rec_maxclients ) { show_crit_box(); print "\t${RED}Vhost count exceeds recommended MaxRequestWorkers limits!${ENDC}\n"} 1523 | } else { 1524 | printf ("${YELLOW}%-75s${ENDC} %-38s\n", "\tYour recommended MaxClients setting (based on available memory) is between $min_rec_maxclients and $max_rec_maxclients${ENDC}.", "<-- Acceptable Range (90-100% of Remaining RAM)"); 1525 | if ( $vhost_count >= $max_rec_maxclients ) { show_crit_box(); print "${RED}Vhost count exceeds recommended MaxClients limits!${ENDC}\n"} 1526 | } 1527 | printf ("%-62s ${CYAN}%d %2s${ENDC}\n", "\tMax potential memory usage:", $max_potential_usage, "MB"); # exempt from NOINFO directive. 1528 | printf ("%-62s ${CYAN}%3.2f %2s${ENDC}\n", "\tPercentage of TOTAL RAM allocated to Apache:", $max_potential_usage_pct_avail, "%"); # exempt from NOINFO directive. 1529 | printf ("%-62s ${CYAN}%3.2f %2s${ENDC}\n", "\tPercentage of REMAINING RAM allocated to Apache:", $max_potential_usage_pct_remain, "%"); # exempt from NOINFO directive. 1530 | } elsif ( $maxclients < $min_rec_maxclients ) { 1531 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1532 | show_crit_box(); print "\t${RED}Your MaxRequestWorkers setting is too low.${ENDC}\n"; # exempt from NOINFO directive. 1533 | } else { 1534 | show_crit_box(); print "\t${RED}Your MaxClients setting is too low.${ENDC}\n"; # exempt from NOINFO directive. 1535 | } 1536 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1537 | printf ("${YELLOW}%-75s${ENDC} %-38s\n", "\tYour recommended MaxRequestWorkers setting is between $min_rec_maxclients and $max_rec_maxclients${ENDC}.", "<-- Acceptable Range (90-100% of Remaining RAM)"); 1538 | if ( $vhost_count >= $max_rec_maxclients ) { show_crit_box(); print "${RED}Vhost count exceeds recommended MaxRequestWorkers limits!${ENDC}\n"} 1539 | } else { 1540 | printf ("${YELLOW}%-75s${ENDC} %-38s\n", "\tYour recommended MaxClients setting (based on available memory) is between $min_rec_maxclients and $max_rec_maxclients${ENDC}.", "<-- Acceptable Range (90-100% of Remaining RAM)"); 1541 | if ( $vhost_count >= $max_rec_maxclients ) { show_crit_box(); print "${RED}Vhost count exceeds recommended MaxClients limits!${ENDC}\n"} 1542 | } 1543 | printf ("%-62s ${CYAN}%d %2s${ENDC}\n", "\tMax potential memory usage:", $max_potential_usage, "MB"); # exempt from NOINFO directive. 1544 | printf ("%-62s ${CYAN}%3.2f %2s${ENDC}\n", "\tPercentage of TOTAL RAM allocated to Apache:", $max_potential_usage_pct_avail, "%"); # exempt from NOINFO directive. 1545 | printf ("%-62s ${CYAN}%3.2f %2s${ENDC}\n", "\tPercentage of REMAINING RAM allocated to Apache:", $max_potential_usage_pct_remain, "%"); # exempt from NOINFO directive. 1546 | } else { 1547 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1548 | show_crit_box(); print "\t${RED}Your MaxRequestWorkers setting is too high.${ENDC}\n"; # exempt from NOINFO directive. 1549 | } else { 1550 | show_crit_box(); print "\t${RED}Your MaxClients setting is too high.${ENDC}\n"; # exempt from NOINFO directive. 1551 | } 1552 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1553 | printf ("${YELLOW}%-75s${ENDC} %-38s\n", "\tYour recommended MaxRequestWorkers setting (based on available memory) is between $min_rec_maxclients and $max_rec_maxclients${ENDC}.", "<-- Acceptable Range (90-100% of Remaining RAM)"); 1554 | if ( $vhost_count >= $max_rec_maxclients ) { show_crit_box(); print "${RED}Vhost count exceeds recommended MaxRequestWorkers limits!${ENDC}\n"} 1555 | } else { 1556 | printf ("${YELLOW}%-75s${ENDC} %-38s\n", "\tYour recommended MaxClients setting (based on available memory) is between $min_rec_maxclients and $max_rec_maxclients${ENDC}.", "<-- Acceptable Range (90-100% of Remaining RAM)"); 1557 | if ( $vhost_count >= $max_rec_maxclients ) { show_crit_box(); print "${RED}Vhost count exceeds recommended MaxClients limits!${ENDC}\n"} 1558 | } 1559 | printf ("%-62s ${RED}%d %2s${ENDC}\n", "\tMax potential memory usage:", $max_potential_usage, "MB"); # exempt from NOINFO directive. 1560 | printf ("%-62s ${RED}%3.2f %2s${ENDC}\n", "\tPercentage of TOTAL RAM allocated to Apache:", $max_potential_usage_pct_avail, "%"); # exempt from NOINFO directive. 1561 | printf ("%-62s ${RED}%3.2f %2s${ENDC}\n", "\tPercentage of REMAINING RAM allocated to Apache:", $max_potential_usage_pct_remain, "%"); # exempt from NOINFO directive. 1562 | } 1563 | # make a logfile entry at /var/log/apache2buddy.log 1564 | open (LOGFILE, ">>/var/log/apache2buddy.log"); 1565 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 1566 | print LOGFILE (date()." Uptime: \"$uptime\" Model: \"Prefork\" Memory: \"$available_mem MB\" MaxRequestWorkers: \"$maxclients\" Recommended: \"$max_rec_maxclients\" Smallest: \"$apache_proc_smallest MB\" Avg: \"$apache_proc_average MB\" Largest: \"$apache_proc_highest MB\" Highest Pct Remaining RAM: \"$max_potential_usage_pct_remain%\" \($max_potential_usage_pct_avail% TOTAL RAM)\n"); 1567 | } else { 1568 | print LOGFILE (date()." Uptime: \"$uptime\" Model: \"Prefork\" Memory: \"$available_mem MB\" Maxclients: \"$maxclients\" Recommended: \"$max_rec_maxclients\" Smallest: \"$apache_proc_smallest MB\" Avg: \"$apache_proc_average MB\" Largest: \"$apache_proc_highest MB\" Highest Pct Remaining RAM: \"$max_potential_usage_pct_remain%\" \($max_potential_usage_pct_avail% TOTAL RAM)\n"); 1569 | } 1570 | close(LOGFILE); 1571 | 1572 | } 1573 | if ($model eq "worker") { 1574 | print "\t${CYAN}Apache appears to be running in worker mode.\n"; 1575 | print "\tPlease check manually for backend processes such as PHP-FPM and pm.max_children.\n"; 1576 | print "\tApache2buddy ONLY calculates maxclients for prefork model.${ENDC}\n"; 1577 | # make a logfile entry at /var/log/apache2buddy.log 1578 | open (LOGFILE, ">>/var/log/apache2buddy.log"); 1579 | print LOGFILE (date()." Uptime: \"$uptime\" Model: \"Worker\" Memory: \"$available_mem MB\" Maxclients: \"$maxclients\" Recommended: \"N\\A\" Smallest: \"$apache_proc_smallest MB\" Avg: \"$apache_proc_average MB\" Largest: \"$apache_proc_highest MB\"\n"); 1580 | close(LOGFILE); 1581 | } 1582 | if ($model eq "event") { 1583 | print "\t${CYAN}Apache appears to be running in event mode.\n"; 1584 | print "\tPlease check manually for backend processes such as PHP-FPM and pm.max_children.\n"; 1585 | print "\tApache2buddy ONLY calculates maxclients for prefork model.${ENDC}\n"; 1586 | # make a logfile entry at /var/log/apache2buddy.log 1587 | open (LOGFILE, ">>/var/log/apache2buddy.log"); 1588 | print LOGFILE (date()." Uptime: \"$uptime\" Model: \"Event\" Memory: \"$available_mem MB\" Maxclients: \"$maxclients\" Recommended: \"N\\A\" Smallest: \"$apache_proc_smallest MB\" Avg: \"$apache_proc_average MB\" Largest: \"$apache_proc_highest MB\"\n"); 1589 | close(LOGFILE); 1590 | } 1591 | our $phpfpm_detected; 1592 | if ( $phpfpm_detected ) { 1593 | insert_hrule(); 1594 | print "${RED}PHP-FPM DETECTED: The results shown may be skewed, check /etc/php-fpm.d/.conf\nfor and calculate pm.max_children separately.${ENDC}\n"; 1595 | print "${RED}Check out Pieter's PHP-FPMPAL (at your own risk) at:\n\n https://github.com/pksteyn/php-fpmpal/\n\nFor advice on your PHP-FPM Pools.${ENDC}\n"; 1596 | } 1597 | 1598 | insert_hrule(); 1599 | if ( ! $NOINFO ) { 1600 | print "A log file entry has been made in: /var/log/apache2buddy.log for future reference.\n\n"; 1601 | print "Last 5 entries:\n\n"; 1602 | my $entries = `tail -5 /var/log/apache2buddy.log`; 1603 | print $entries."\n"; 1604 | } 1605 | } 1606 | 1607 | # this rounds a value to the nearest hundreth 1608 | sub round { 1609 | my ( $value ) = @_; 1610 | 1611 | # add five thousandths 1612 | $value = $value + 0.005; 1613 | 1614 | # truncat the result 1615 | $value = sprintf("%.2f", $value); 1616 | 1617 | return $value; 1618 | } 1619 | 1620 | # print a header 1621 | sub print_header { 1622 | my ($servername, $ipaddr) = @_; 1623 | if ( ! $NOHEADER ) { 1624 | my $headerstring = "apache2buddy.pl report for $servername ($ipaddr)"; 1625 | my $hrline = "#" x length($headerstring); 1626 | print ${GREEN} . $hrline . ${ENDC} . "\n"; 1627 | print $headerstring . "\n"; 1628 | print ${GREEN} . $hrline . ${ENDC} . "\n"; 1629 | } 1630 | } 1631 | 1632 | sub show_debug_box { 1633 | print "[ ${BOLD}${BLUE}??${ENDC} ] "; 1634 | } 1635 | 1636 | sub show_advisory_box { 1637 | print "[ $BOLD${YELLOW}\@\@${ENDC} ] "; 1638 | } 1639 | 1640 | sub show_info_box { 1641 | print "[ ${BOLD}${BLUE}--${ENDC} ] "; 1642 | } 1643 | 1644 | sub show_ok_box { 1645 | print "[ ${BOLD}${GREEN}OK${ENDC} ] "; 1646 | } 1647 | 1648 | sub show_warn_box { 1649 | print "[ ${BOLD}${YELLOW}>>${ENDC} ] "; 1650 | } 1651 | 1652 | sub show_crit_box { 1653 | print "[ ${BOLD}${RED}!!${ENDC} ] "; 1654 | } 1655 | 1656 | sub show_shortok_box { 1657 | print "[ ${GREEN}OK${ENDC} ]"; 1658 | } 1659 | 1660 | sub show_important_message { 1661 | if ( ! $NOINFO ) { 1662 | print "\n${RED}** IMPORTANT MESSAGE **\n\napache2buddy is not a troubleshooting tool.\nDo not use it to try and determine why your site\nwent down or why it was slow.\n\nPerform some proper investigations first, and\nonly if you found that you were hitting the\nMaxRequestWorkers limit, or if your server was\nrunning out of memory (primarily due to\nexcessive memory usage by Apache), should you\nrun this script and refer to its output..\n\n** DOMAIN EXPIRY NOTICE **\n\nThere is no intention of renewing the domain: \n\n\tapache2buddy.pl\n\nPlease use extreme caution! Running arbirary\ncode as root is high risk and the domain\nwill not be associated with this code.\n\nWe took steps in 2018 to move the code to\ngithub exclusively and have it run from\nthere directly with curl and perl.${ENDC}\n"; 1663 | } 1664 | } 1665 | 1666 | sub insert_hrule() { 1667 | print "-" x 80; 1668 | print "\n"; 1669 | } 1670 | 1671 | sub preflight_checks { 1672 | # There will be other showstoppers that become apparent as the script develops in execution, 1673 | # however we can capture the common "basic errors" here, and gracefully exit with useful errors. 1674 | 1675 | # Check 1 1676 | # make sure the script is being run as root. 1677 | # we need to run as root to ensure that we can access all of the appropriate 1678 | # files 1679 | my $command; 1680 | my $uid = `id -u`; 1681 | chomp($uid); 1682 | 1683 | print "VERBOSE: UID of user is: ".$uid."\n" if $VERBOSE; 1684 | 1685 | if ( $uid ne '0' ) { 1686 | show_crit_box(); 1687 | print "This script must be run as root.\n"; 1688 | exit 1; 1689 | } else { 1690 | if ( ! $NOOK ) { show_ok_box(); print "This script is being run as root.\n" } 1691 | } 1692 | 1693 | # Check 2 1694 | # this script uses pmap to determine the memory mapped to each apache 1695 | # process. make sure that pmap is available. 1696 | our $pmap = `which pmap`; 1697 | chomp($pmap); 1698 | 1699 | # make sure that pmap is available within our path 1700 | if ( $pmap !~ m/.*\/pmap/ ) { 1701 | show_crit_box(); 1702 | print "Unable to locate the pmap utility. This script requires pmap to analyze Apache's memory consumption.\n"; 1703 | exit 1; 1704 | } else { 1705 | if ( ! $NOOK ) { show_ok_box(); print "The utility 'pmap' exists and is available for use: ${CYAN}$pmap${ENDC}\n" } 1706 | } 1707 | 1708 | # Check 2.1 1709 | # this script uses ss and falls back to netstat to determine the port that apache is listening on 1710 | $ss_path = `which ss 2>/dev/null`; 1711 | chomp($ss_path); 1712 | $netstat_path = `which netstat 2>/dev/null`; 1713 | chomp($netstat_path); 1714 | 1715 | if ($ss_path) { 1716 | show_ok_box(); print "Using 'ss' for socket statistics.\n"; 1717 | } elsif ($netstat_path) { 1718 | show_ok_box(); print "Using 'netstat' for socket statistics.\n"; 1719 | } else { 1720 | show_crit_box(); print "Neither 'ss' nor 'netstat' is available. Please install one of them.\n"; 1721 | show_info_box(); print "${YELLOW}To fix this make sure either the iproute2 or net-tools package is installed.${ENDC}\n"; 1722 | exit 1 1723 | } 1724 | 1725 | # Check 3 1726 | # make sure PHP is available before we proceed 1727 | # check to see if there is a binary called "php" in our path 1728 | my $check = `which php`; 1729 | chomp ($check); 1730 | if ( $check !~ m/.*\/php/ ) { 1731 | if ( ! $NOWARN ) { 1732 | show_advisory_box(); 1733 | print "${YELLOW}Unable to locate the PHP binary. PHP specific checks will be skipped.${ENDC}\n"; 1734 | } 1735 | our $PHP = 0; 1736 | my $path = `echo \$PATH`; 1737 | chomp($path); 1738 | print "VERBOSE: Path: $path\n" if $VERBOSE; 1739 | } else { 1740 | if ( ! $NOOK ) { show_ok_box(); print "'php' exists and is available for use: ${CYAN}$check${ENDC}\n" } 1741 | our $PHP = 1; 1742 | } 1743 | 1744 | # check 3.1 1745 | # Check for which apachectl we are to use 1746 | # check to see if there is a binary called "apachectl" in our path 1747 | our $apachectl= `which apachectl`; 1748 | chomp($apachectl); 1749 | 1750 | if ( $apachectl !~ m/.*\/apachectl/ ) { 1751 | show_info_box(); 1752 | print "Unable to locate the apachectl utility. This script requires apachectl to analyze Apache's vhost configurations.\n"; 1753 | show_info_box(); 1754 | print "Not fatal yet, trying to locate the apache2ctl utility instead.\n"; 1755 | # check to see if there is a binary called "apache2ctl" in our path 1756 | our $apachectl= `which apache2ctl`; 1757 | chomp($apachectl); 1758 | 1759 | if ( $apachectl !~ m/.*\/apache2ctl/ ) { 1760 | show_crit_box(); 1761 | print "Unable to locate the apache2ctl utility. This script now requires apache2ctl to analyze Apache's vhost configurations.\n"; 1762 | show_info_box(); 1763 | print "It looks like you might be running something else, other than apache..\n"; 1764 | exit 1; 1765 | } else { 1766 | if ( ! $NOOK ) { show_ok_box(); print "The utility 'apache2ctl' exists and is available for use: ${CYAN}$apachectl${ENDC}\n" } 1767 | } 1768 | 1769 | } else { 1770 | if ( ! $NOOK ) { show_ok_box(); print "The utility 'apachectl' exists and is available for use: ${CYAN}$apachectl${ENDC}\n" } 1771 | } 1772 | 1773 | 1774 | 1775 | # Check 4 1776 | # Check for valid port 1777 | if ( $port < 0 || $port > 65534 ) { 1778 | show_crit_box(); 1779 | print "INVALID PORT: $port. "; 1780 | print "Valid port numbers are 1-65534.\n"; 1781 | exit 1; 1782 | } else { 1783 | if ( ! $NOOK ) { show_ok_box(); print "The port \(port ${CYAN}$port${ENDC}\) is a valid port.\n" } 1784 | } 1785 | 1786 | # Check 5 1787 | # Get OS Name and Version 1788 | if ( ! $NOINFO ) { show_info_box(); print "We are attempting to discover the operating system type and version number ...\n" } 1789 | my ($distro, $version, $codename) = get_os_platform(); 1790 | if ( ! $NOINFO ) { show_info_box(); print "Distro: ${CYAN}" . $distro . "${ENDC}\n"} 1791 | if ( ! $NOINFO ) { show_info_box(); print "Version: ${CYAN}" . $version . "${ENDC}\n"} 1792 | if ( ! $NOINFO ) { show_info_box(); print "Codename: ${CYAN}" . $codename . "${ENDC}\n"} 1793 | if ( ! $NOCHKOS ) { 1794 | check_os_support($distro, $version, $codename); 1795 | } else { 1796 | show_warn_box(); print "${YELLOW}OS Version Checks were skipped by user directive, you may get errors.${ENDC}\n"; 1797 | } 1798 | 1799 | # get our hostname 1800 | our $servername = get_hostname(); 1801 | if ( ! $NOINFO ) { show_info_box(); print "Hostname: ${CYAN}$servername${ENDC}\n" } 1802 | 1803 | # get our ip address 1804 | our $public_ip_address = get_ip(); 1805 | if ( ! $NOINFO ) { show_info_box(); print "Primary IP: ${CYAN}$public_ip_address${ENDC}\n" } 1806 | 1807 | 1808 | # Check 6 1809 | # first thing we do is get the pid of the process listening on the 1810 | # specified port 1811 | if ( ! $NOINFO ) { show_info_box(); print "We are checking the service running on port ${CYAN}$port${ENDC}...\n" } 1812 | 1813 | our $pid; 1814 | if (! $pid ) { 1815 | our $pid = get_pid($port); 1816 | } 1817 | 1818 | print "VERBOSE: PID is ".$pid."\n" if $VERBOSE; 1819 | 1820 | if ( $pid eq 0 ) { 1821 | if ( ! $NOWARN ) { 1822 | show_warn_box; print "${YELLOW}Nothing seems to be listening on port $port.${ENDC} Falling back to process list...\n"; 1823 | } 1824 | my @process_info = split(' ', `ps -C 'httpd httpd.worker apache apache2 /usr/sbin/httpd /usr/sbin/httpd.worker' -f | grep '^root'`); 1825 | $pid = $process_info[1]; 1826 | if ( not $pid ) { 1827 | show_crit_box; print "apache process not found.\n"; 1828 | exit 1; 1829 | } else { 1830 | if ($ss_path) { 1831 | $command = `$ss_path -plnt | egrep "httpd|apache2"`; 1832 | } elsif ($netstat_path) { 1833 | $command = `$netstat_path -plnt | egrep "httpd|apache2"`; 1834 | } 1835 | if ( $command =~ /:+(\d+)/ ) { our $real_port = $1 } 1836 | our $real_port; 1837 | our $process_name = get_process_name($pid); 1838 | our $apache_version = get_apache_version($process_name); 1839 | if ( ! $NOINFO ) { show_info_box; print "Apache is actually listening on port ${CYAN}$real_port${ENDC}\n" } 1840 | if ( ! $NOINFO ) { show_info_box; print "The process running on port ${CYAN}$real_port${ENDC} is ${CYAN}$apache_version${ENDC}.\n" } 1841 | # Issue #252 apache 2.2 is EOL 1842 | # Issue #325 bug - no sanity checking 1843 | my $eol_version = "2.2"; 1844 | if ( index($apache_version,$eol_version) != -1) { 1845 | # index returns -1 if the string is NOT present, if it is present, the following will be run: 1846 | # ie we only care if the version is detected as being 2.2 - refer to issue #325 as above mentioned. 1847 | if ( ! $NOINFO ) { show_crit_box; print "${YELLOW}Apache 2.2 is End Of Life. For more Information, see ${CYAN}https://httpd.apache.org/.${ENDC}" } 1848 | } 1849 | } 1850 | } else { 1851 | # now we get the name of the process running with the specified pid 1852 | our $process_name = get_process_name($pid); 1853 | if ( ! $NOINFO ) { show_info_box; print "The process listening on port ${CYAN}$port${ENDC} is ${CYAN}$process_name${ENDC}\n" } 1854 | 1855 | if ( $process_name eq 0 ) { 1856 | show_crit_box(); 1857 | print "Unable to determine the name of the process. Is apache running on this server?\n"; 1858 | exit 1; 1859 | } 1860 | 1861 | # Check 7 1862 | # check to see if the process we have identified is Apache 1863 | our $is_it_apache = test_process($process_name); 1864 | 1865 | if ( $is_it_apache == 1 ) { 1866 | our $apache_version = get_apache_version($process_name); 1867 | 1868 | print "VERBOSE: Apache version: $apache_version\n" if $VERBOSE; 1869 | 1870 | # if we received a "0", just print "Apache" 1871 | if ( $apache_version eq 0 ) { 1872 | $apache_version = "Apache"; 1873 | } 1874 | if ( ! $NOINFO ) { show_info_box; print "The process running on port ${CYAN}$port${ENDC} is ${CYAN}$apache_version${ENDC}.\n" } 1875 | } else { 1876 | if ( ! $NOINFO ) { show_info_box; print "The process running on port $port is not Apache. Falling back to process list...\n" } 1877 | my @process_info = split(' ', `ps -C 'httpd httpd.worker apache apache2 /usr/sbin/httpd /usr/sbin/httpd.worker' -f | grep '^root'`); 1878 | $pid = $process_info[1]; 1879 | 1880 | if ( !$pid ) { 1881 | show_crit_box(); 1882 | print "Could not find Apache process. Exiting...\n"; 1883 | exit 1; 1884 | } else { 1885 | # If we found it, then reset the proces_name, and version. 1886 | $process_name = get_process_name($pid); 1887 | our $apache_version = get_apache_version($process_name); 1888 | # also report what port apache is actually listening on. 1889 | if ($ss_path) { 1890 | $command = `$ss_path -plnt | egrep "httpd|apache2"`; 1891 | } elsif ($netstat_path) { 1892 | $command = `$netstat_path -plnt | egrep "httpd|apache2"`; 1893 | } 1894 | if ( $command =~ /:+(\d+)/ ) { our $real_port = $1 } 1895 | our $real_port; 1896 | if ( ! $NOINFO ) { show_info_box; print "Apache is actually listening on port ${CYAN}$real_port${ENDC}\n" } 1897 | if ( ! $NOINFO ) { show_info_box; print "The process running on port ${CYAN}$real_port${ENDC} is ${CYAN}$apache_version${ENDC}.\n" } 1898 | } 1899 | } 1900 | } 1901 | 1902 | 1903 | # Check 8 1904 | # Due to logic error, moved this check to 13.2 1905 | # Check apache uptime needs parent PID not a child pid. 1906 | 1907 | # Check 9 1908 | # find the apache root 1909 | our $process_name; 1910 | our $apache_root = get_apache_root($process_name); 1911 | print "VERBOSE: The Apache root is: ".$apache_root."\n" if $VERBOSE; 1912 | 1913 | # check 10 1914 | # find the apache configuration file (relative to the apache root) 1915 | our $apache_conf_file = get_apache_conf_file($process_name); 1916 | print "VERBOSE: The Apache config file is: ".$apache_conf_file."\n" if $VERBOSE; 1917 | 1918 | # check 11 1919 | # piece together the full path to the configuration file, if a server 1920 | # does not have the HTTPD_ROOT value defined in its apache build, then 1921 | # try just using the path to the configuration file 1922 | our $full_apache_conf_file_path; 1923 | if ( -e $apache_conf_file ) { 1924 | $full_apache_conf_file_path = $apache_conf_file; 1925 | if ( ! $NOINFO ) { show_info_box(); print "The full path to the Apache config file is: ${CYAN}$full_apache_conf_file_path${ENDC}\n" } 1926 | } elsif ( -e $apache_root."/".$apache_conf_file ) { 1927 | $full_apache_conf_file_path = $apache_root."/".$apache_conf_file; 1928 | if ( ! $NOINFO ) { show_info_box(); print "The full path to the Apache config file is: ${CYAN}$full_apache_conf_file_path${ENDC}\n" } 1929 | } else { 1930 | show_crit_box(); 1931 | print "Apache configuration file does not exist: ".$full_apache_conf_file_path."\n"; 1932 | exit 1; 1933 | } 1934 | 1935 | # check 12 1936 | # find out what model we are running 1937 | our $model = get_apache_model($process_name); 1938 | if ( $model eq 0 ) { 1939 | show_crit_box(); 1940 | print "Unable to determine whether Apache is using worker or prefork\n"; 1941 | exit 1; 1942 | } else { 1943 | # account for '\x{d}' strangeness 1944 | $model =~ s/\x{d}//; 1945 | if ( ! $NOINFO ) { show_info_box(); print "Apache is using ${CYAN}$model${ENDC} model.\n" } 1946 | } 1947 | 1948 | # Check 13 1949 | # get the entire config, including included files, into an array 1950 | our @config_array = build_config_array($full_apache_conf_file_path,$apache_root); 1951 | # determine what user apache runs as (according to the config file) 1952 | our $apache_user_config = find_master_value(\@config_array, $model, 'user'); 1953 | # account for 'apache\x{d}' strangeness 1954 | $apache_user_config =~ s/\x{d}//; 1955 | $apache_user_config =~ s/^\s*(.*?)\s*$/$1/;; # address issue #19, strip whitespace from both sides. 1956 | # issue #348 sanity check for NOT FOUND on ubuntu systems 1957 | if ($apache_user_config eq "CONFIG NOT FOUND") { 1958 | if ($VERBOSE) { print "VERBOSE: Checking for envvarsfile to get apache_config_user on ubuntu systems.\n" } 1959 | if ( -f "/etc/apache2/envvars" && -r "/etc/apache2/envvars") { 1960 | if ($VERBOSE) { print "VERBOSE: /etc/apache2/envvars exists and is readable, checking value of APACHE_RUN_USER...\n" } 1961 | $apache_user_config = `grep 'export APACHE_RUN_USER=' /etc/apache2/envvars | awk -F"=" '{print \$2}'`; 1962 | chomp($apache_user_config); 1963 | $apache_user_config =~ s/^\s*(.*?)\s*$/$1/;; # address issue #19, strip whitespace from both sides. 1964 | } 1965 | } 1966 | unless ($apache_user_config eq "apache" or $apache_user_config eq "www-data" or ($apache_user_config eq "wwwrun" and $distro eq "SUSE Linux Enterprise Server")) { 1967 | my $apache_config_userid = `id -u $apache_user_config`; 1968 | chomp($apache_config_userid); 1969 | # account for 'apache\x{d}' strangeness 1970 | $apache_user_config =~ s/\x{d}//; 1971 | show_warn_box(); print ("${RED}Non-standard apache set-up, apache is configured to run as UID: ${CYAN}$apache_config_userid${RED} (${CYAN}$apache_user_config${RED}). Expecting UID 48 ('apache' or 'www-data').${ENDC}\n"); 1972 | 1973 | } 1974 | 1975 | our $process_name; 1976 | # determine what user apache runs as (according to the processlist) 1977 | our $apache_user_running = `ps aux | grep $process_name | grep -v ^root | awk \'{ print \$1 }\' | uniq`; 1978 | chomp($apache_user_running); 1979 | 1980 | # compare the running user with the config user: 1981 | if ( $apache_user_running eq $apache_user_config) { 1982 | if ( ! $NOOK ) { show_ok_box(); print "Running user (${CYAN}$apache_user_running${ENDC}) matches config (${CYAN}$apache_user_config${ENDC}).\n" } 1983 | 1984 | } else { 1985 | if ( ! $NOWARN ) { show_warn_box(); print "Running user (${CYAN}$apache_user_running${ENDC}) does NOT match config (${CYAN}$apache_user_config${ENDC}).\n" } 1986 | } 1987 | 1988 | if (length($apache_user_running) > 8) { 1989 | # Now we have to keep the first 7 characters and change the 8th character to a + sign, eg 'developer' becomes 'develop+' 1990 | my $original_user = $apache_user_running; 1991 | $apache_user_running = substr($original_user, 0, 7)."+"; 1992 | } 1993 | 1994 | # Check 13.1 1995 | # Determine the size of the parent process 1996 | # Bug Out if greater than 50MB 1997 | # Issue #309 encapsulate check 13.1 as a whole 1998 | # to respect the --no-check-pid option 1999 | if ( ! $NOCHKPID) { 2000 | our $pidfile_cfv = find_master_value(\@config_array, $model, 'pidfile'); 2001 | if ( ! $NOINFO ) { show_info_box; print "pidfile setting is ${CYAN}$pidfile_cfv${ENDC}.\n" } 2002 | # addressing issue #84, I realised this whole block of code is guessing, I understand why, but its not sane. 2003 | # for example what we need to do is first check if the path is a relative path or absolute path. 2004 | # If it is an absolute path, lets check that first, which will cut out a lot of unnecessary code, 2005 | # otherwise we can start guessing based on common relative paths. 2006 | # Fix for Issue #222 strip any quotes from returned string 2007 | # "/var/run/httpd.pid" becomes /var/run/httpd.pid 2008 | if ($VERBOSE) { print "VERBOSE: Stripping any quotes from string ...\n" } 2009 | if ($VERBOSE) { print "VERBOSE: BEFORE ($pidfile_cfv).\n" } 2010 | $pidfile_cfv =~ s/^"(.*)"$/$1/; 2011 | $pidfile_cfv =~ s/^'(.*)'$/$1/; 2012 | if ($VERBOSE) { print "VERBOSE: AFTER ($pidfile_cfv).\n" } 2013 | our $pidfile_pdv = get_apache_pid_file($process_name); 2014 | if ( substr($pidfile_pdv, 0, 1) ne "/" ) { 2015 | $pidfile_pdv = $apache_root."/".$pidfile_pdv; 2016 | } 2017 | if ( -f $pidfile_cfv ) { 2018 | our $pidfile =$pidfile_cfv; 2019 | } elsif ( -f $pidfile_pdv ) { 2020 | our $pidfile = $pidfile_pdv; 2021 | } else { 2022 | if ($pidfile_cfv eq "run/httpd.pid") { 2023 | # it could be in a couple of places, so lets test! 2024 | if (-f "/var/run/httpd/httpd.pid") { 2025 | our $pidfile = "/var/run/httpd/httpd.pid"; 2026 | } elsif (-f "/run/httpd/httpd.pid") { 2027 | our $pidfile = "/run/httpd/httpd.pid"; 2028 | } elsif (-f "/var/run/httpd.pid") { 2029 | our $pidfile = "/var/run/httpd.pid"; 2030 | } else { 2031 | if ( ! $NOINFO ) { show_crit_box; print "${RED}Unable to locate pid file${ENDC}. Exiting.\n" } 2032 | exit 1; 2033 | } 2034 | } elsif ($pidfile_cfv eq "/var/run/apache2/apache2\$SUFFIX.pid") { 2035 | our $pidfile = "/var/run/apache2/apache2.pid"; 2036 | } elsif ($pidfile_cfv eq "/var/run/apache2\$SUFFIX.pid") { 2037 | our $pidfile = "/var/run/apache2.pid"; 2038 | } elsif ($pidfile_cfv eq "/var/run/apache2\$SUFFIX/apache2.pid") { 2039 | our $pidfile = "/var/run/apache2/apache2.pid"; 2040 | } else { 2041 | # revert to a find command as a last ditch effort to find the pid 2042 | if ($VERBOSE) { print "VERBOSE: Looking for pid file ...\n" } 2043 | if ( -d "/var/run/apache2") { 2044 | our $pidguess = `find /var/run/apache2 | grep pid`; 2045 | } elsif ( -d "/run/httpd") { 2046 | our $pidguess = `find /run/httpd | grep pid`; 2047 | } elsif ( -d "/var/run/httpd") { 2048 | our $pidguess = `find /var/run/httpd | grep pid`; 2049 | } elsif ( -d "/opt/apache2/run") { 2050 | our $pidguess = `find /opt/apache2/run | grep pid`; 2051 | } else { 2052 | show_crit_box; print "${RED}Unable to locate pid file${ENDC}. Exiting.\n"; 2053 | exit 1; 2054 | } 2055 | our $pidguess; 2056 | chomp($pidguess); 2057 | if ( -f $pidguess ) { 2058 | our $pidfile = $pidguess; 2059 | if ($VERBOSE) { print "VERBOSE: Located pidfile at $pidfile.\n" } 2060 | } else { 2061 | show_crit_box; print "${RED}Unable to locate pid file${ENDC}. Exiting.\n"; 2062 | exit 1; 2063 | } 2064 | } 2065 | } 2066 | 2067 | our $pidfile; 2068 | if (-f $pidfile) { 2069 | if ( ! $NOINFO ) { show_info_box; print "Actual pidfile is ${CYAN}$pidfile${ENDC}.\n" } 2070 | } else { 2071 | if ( ! $NOINFO ) { show_crit_box; print "${RED}Unable to open pid file $pidfile${ENDC}. Exiting.\n" } 2072 | exit 1; 2073 | } 2074 | # get pid 2075 | our $pidfile; 2076 | our $parent_pid = `cat $pidfile`; 2077 | chomp($parent_pid); 2078 | if ( ! $NOINFO ) { show_info_box; print "Parent PID: ${CYAN}$parent_pid${ENDC}.\n" } 2079 | if ( ! $NOCHKPID) { 2080 | if ($VERBOSE) { print "VERBOSE: output of 'pmap' is different depending on distro!\n" } 2081 | my $ppid_mem_usage; 2082 | if (ucfirst($distro) eq "SUSE Linux Enterprise Server" ) { 2083 | $ppid_mem_usage = `LANGUAGE=en_GB.UTF-8 pmap -d $parent_pid | egrep "writable-private" | awk \'{ print \$1 }\'`; 2084 | } else { 2085 | $ppid_mem_usage = `LANGUAGE=en_GB.UTF-8 pmap -d $parent_pid | egrep "writeable/private" | awk \'{ print \$4 }\'`; 2086 | } 2087 | $ppid_mem_usage =~ s/K//; 2088 | chomp($ppid_mem_usage); 2089 | if ($ppid_mem_usage > 50000) { 2090 | show_crit_box; print "${RED}Memory usage of parent PID is greater than 50MB: $ppid_mem_usage Kilobytes${ENDC}.\n"; 2091 | show_advisory_box; print "${YELLOW}For more information, see ${CYAN}https://github.com/richardforth/apache2buddy/wiki/50MB-Parent-PID-Issue${ENDC}\n"; 2092 | show_advisory_box; print "${YELLOW}If you are desperate, try ${CYAN}-P${YELLOW} or ${CYAN}--no-check-pid${ENDC}${YELLOW}.${ENDC}\n"; 2093 | show_info_box; print "Exiting.\n"; 2094 | exit 1; 2095 | } else { 2096 | if ( ! $NOOK ) { show_ok_box; print "Memory usage of parent PID is less than 50MB: ${CYAN}$ppid_mem_usage Kilobytes${ENDC}.\n" } 2097 | } 2098 | } 2099 | } else { 2100 | if ( ! $NOINFO ) { show_info_box; print "Parent PID checks skipped because --no-check-pid option used.{$ENDC}.\n" } 2101 | } 2102 | 2103 | # Check 13.2 2104 | # determine the Apache uptime 2105 | if ( ! $NOCHKPID) { 2106 | our $parent_pid; 2107 | our @apache_uptime = get_apache_uptime($parent_pid); 2108 | 2109 | if ( ! $NOINFO ) { show_info_box(); print "Apache has been running ${CYAN}$apache_uptime[0]${ENDC}d ${CYAN}$apache_uptime[1]${ENDC}h ${CYAN}$apache_uptime[2]${ENDC}m ${CYAN}$apache_uptime[3]${ENDC}s.\n" } 2110 | if ( $apache_uptime[0] == "0" ) { 2111 | if ( ! $NOWARN ) { 2112 | show_crit_box(); print "${RED}*** LOW UPTIME ***${ENDC}.\n"; 2113 | show_advisory_box(); print "${YELLOW}The following recommendations may be misleading - apache has been restarted within the last 24 hours.${ENDC}\n"; 2114 | } 2115 | } 2116 | } else { 2117 | if ( ! $NOINFO ) { show_info_box; print "Uptime checks skipped because --no-check-pid option used.{$ENDC}.\n" } 2118 | } 2119 | 2120 | # check 13.3 2121 | # figure out how much RAM is in the server 2122 | our $available_mem = `LANGUAGE=en_GB.UTF-8 free | grep \"Mem:\" | awk \'{ print \$2 }\'` / 1024; 2123 | $available_mem = floor($available_mem); 2124 | 2125 | if ( ! $NOINFO ) { show_info_box(); print "Your server has ${CYAN}$available_mem MB${ENDC} of PHYSICAL memory.\n" } 2126 | 2127 | # Check 14 2128 | # Get serverlimit value 2129 | our $serverlimit = find_master_value(\@config_array, $model, 'serverlimit'); 2130 | if($serverlimit eq 'CONFIG NOT FOUND') { 2131 | if ( ! $NOWARN ) { show_warn_box; print "ServerLimit directive not found, assuming default values.\n" } 2132 | our $model; 2133 | if ( $model eq "prefork") { 2134 | # Default for prefork - see http://httpd.apache.org/docs/current/mod/mpm_common.html#serverlimit 2135 | $serverlimit = 256; # yes, yes I know, but keeping the variable name saves a whole bunch of code just for semantics. 2136 | } else { 2137 | # Default for Worker 2138 | $serverlimit = 16; # yes, yes I know, but keeping the variable name saves a whole bunch of code just for semantics. 2139 | } 2140 | } 2141 | # account for '\x{d}' strangeness 2142 | $serverlimit =~ s/\x{d}//; 2143 | if ( ! $NOINFO ) { show_info_box(); print "Your ServerLimit setting is ${CYAN}$serverlimit${ENDC}.\n" } 2144 | 2145 | # Check 15 2146 | # calculate ThreadsPerChild. This is useful for the worker MPM calculations 2147 | if ( $model eq "worker" || $model eq "event" ) { 2148 | our $threadsperchild = find_master_value(\@config_array, $model, 'threadsperchild'); 2149 | if($threadsperchild eq 'CONFIG NOT FOUND') { 2150 | if ( ! $NOWARN ) { show_warn_box; print "ThreadsPerChild directive not found, assuming default values.\n" } 2151 | $threadsperchild = 25; 2152 | } 2153 | if ( ! $NOINFO ) { show_info_box(); print "Your ThreadsPerChild setting is ${CYAN}$threadsperchild${ENDC}.\n" } 2154 | } 2155 | our $threadsperchild; 2156 | if ($model eq "worker" || $model eq "event" ) { 2157 | if ( ! $NOINFO ) { show_info_box(); print "Your ThreadsPerChild setting for worker/event MPM is ".$threadsperchild."\n" } 2158 | if ( ! $NOINFO ) { show_info_box(); print "Your ServerLimit setting for worker/event MPM is ".$serverlimit."\n" } 2159 | } 2160 | 2161 | # Check 16 2162 | # determine what the max clients setting is 2163 | # if apache2.4 get maxrequestworkers 2164 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 2165 | our $maxclients = find_master_value(\@config_array, $model, 'maxrequestworkers'); 2166 | if($maxclients eq 'CONFIG NOT FOUND') { 2167 | # Fallback to MaxClients if MaxRequestsPerWorker not found 2168 | $maxclients = find_master_value(\@config_array, $model, 'maxclients'); 2169 | } 2170 | if($maxclients eq 'CONFIG NOT FOUND') { 2171 | if ( ! $NOWARN ) { show_warn_box; print "MaxRequestWorkers directive not found, assuming default values.\n" } 2172 | if ( $model eq "prefork") { 2173 | # Default for prefork - see http://httpd.apache.org/docs/2.4/mod/mpm_common.html#maxrequestworkers 2174 | $maxclients = 256; # yes, yes I know, but keeping the variable name saves a whole bunch of code just for semantics. 2175 | } else { 2176 | # Default for Worker 2177 | $maxclients = $serverlimit * $threadsperchild; # yes, yes I know, but keeping the variable name saves a whole bunch of code just for semantics. 2178 | } 2179 | 2180 | } 2181 | # account for '\x{d}' strangeness 2182 | $maxclients =~ s/\x{d}//; 2183 | $maxclients =~ s/\s//; 2184 | if ( ! $NOINFO ) { show_info_box(); print "Your MaxRequestWorkers setting is ${CYAN}$maxclients${ENDC}.\n" } 2185 | } else { 2186 | # otherwise, assume 2.2 and get maxclients 2187 | our $maxclients = find_master_value(\@config_array, $model, 'maxclients'); 2188 | if($maxclients eq 'CONFIG NOT FOUND') { 2189 | if ( ! $NOWARN ) { show_warn_box; print "MaxClients directive not found, assuming default values.\n" } 2190 | if ( $model eq "prefork") { 2191 | # Default for prefork - see https://httpd.apache.org/docs/2.2/en/mod/mpm_common.html#maxclients 2192 | $maxclients = 256; # yes, yes I know, but keeping the variable name saves a whole bunch of code just for semantics. 2193 | } else { 2194 | # Default for Worker 2195 | $maxclients = $serverlimit * $threadsperchild; # yes, yes I know, but keeping the variable name saves a whole bunch of code just for semantics. 2196 | } 2197 | } 2198 | # account for '\x{d}' strangeness 2199 | $maxclients =~ s/\x{d}//; 2200 | $maxclients =~ s/\s//; 2201 | if ( ! $NOINFO ) { show_info_box(); print "Your MaxClients setting is ${CYAN}$maxclients${ENDC}.\n" } 2202 | } 2203 | 2204 | # Check 16.01 2205 | # Check if maxclients is more than ServerLimit (Only applies to prefork) 2206 | # Then set maxclients to serverlimit if serverlimit is LESS than MaxClients 2207 | our $maxclients; 2208 | our $serverlimit; 2209 | if ($model eq "prefork") { 2210 | if ($maxclients > $serverlimit) { 2211 | $maxclients = $serverlimit; 2212 | if ( ! $NOWARN ) { show_warn_box; print "MaxClients directive is higher than ServerLimit, using ServerLimit ($serverlimit) to apply calculations.\n" } 2213 | } 2214 | } 2215 | 2216 | 2217 | # Check 16.1 2218 | # Get current number of running apache processes 2219 | # This resolves Issue #15: https://github.com/richardforth/apache2buddy/issues/15 2220 | our $maxclients; 2221 | our $current_proc_count = `ps aux | egrep "httpd|apache2" | grep -v rotatelogs | grep -v apache2buddy | grep -v grep | wc -l`; 2222 | chomp ($current_proc_count); 2223 | if ($current_proc_count >= $maxclients) { 2224 | if ( ! $NOWARN ) { show_warn_box(); print "Current Apache Process Count is ${RED}$current_proc_count${ENDC}, including the parent PID.\n" } 2225 | } else { 2226 | if ( ! $NOOK ) { show_ok_box(); print "Current Apache Process Count is ${CYAN}$current_proc_count${ENDC}, including the parent PID.\n"} 2227 | } 2228 | 2229 | # Check 16.2 2230 | # Get current number of vhosts 2231 | # This addresses issue #5 'count of vhosts': https://github.com/richardforth/apache2buddy/issues/5 2232 | # address https://github.com/richardforth/apache2buddy/issues/239 Plesk vhost counts always out 2233 | our $vhost_count = `LANGUAGE=en_GB.UTF-8 $apachectl -S 2>&1 | egrep -v "lists|default|webmail" | grep -c "[ ]\\{1,\\}port [0-9]\\{1,\\}"`; 2234 | # split this total into port 80 and 443 vhosts respectively: https://github.com/richardforth/apache2buddy/issues/142 2235 | our $port80vhost_count = `LANGUAGE=en_GB.UTF-8 $apachectl -S 2>&1 | egrep -v "lists|default|webmail" | grep -c "port 80 "`; 2236 | our $port443vhost_count = `LANGUAGE=en_GB.UTF-8 $apachectl -S 2>&1 | egrep -v "lists|default|webmail" | grep -c "port 443 "`; 2237 | our $port7080vhost_count = `LANGUAGE=en_GB.UTF-8 $apachectl -S 2>&1 | egrep -v "lists|default|webmail" | grep -c "port 7080 "`; 2238 | our $port7081vhost_count = `LANGUAGE=en_GB.UTF-8 $apachectl -S 2>&1 | egrep -v "lists|default|webmail" | grep -c "port 7081 "`; 2239 | # in case apache2ctl not working, try apachectl 2240 | chomp ($vhost_count); 2241 | chomp ($port80vhost_count); 2242 | chomp ($port443vhost_count); 2243 | chomp ($port7080vhost_count); 2244 | chomp ($port7081vhost_count); 2245 | if ( ! $NOINFO ) { show_info_box(); print "Number of vhosts detected: ${CYAN}$vhost_count${ENDC}.\n" } 2246 | if ($port80vhost_count gt 0 ) { 2247 | if ( ! $NOINFO ) { show_info_box(); print " |________ of which ${CYAN}$port80vhost_count${ENDC} are HTTP (specifically, port 80).\n" } 2248 | } 2249 | if ($port443vhost_count gt 0 ) { 2250 | if ( ! $NOINFO ) { show_info_box(); print " |________ of which ${CYAN}$port443vhost_count${ENDC} are HTTPS (specifically, port 443).\n" } 2251 | } 2252 | if ($port7080vhost_count gt 0 ) { 2253 | if ( ! $NOINFO ) { show_info_box(); print " |________ of which ${CYAN}$port7080vhost_count${ENDC} are HTTP (specifically, port 7080).\n" } 2254 | } 2255 | if ($port7081vhost_count gt 0 ) { 2256 | if ( ! $NOINFO ) { show_info_box(); print " |________ of which ${CYAN}$port7081vhost_count${ENDC} are HTTPS (specifically, port 7081).\n" } 2257 | } 2258 | our $real_port; 2259 | if ($real_port) { 2260 | if ( not( $real_port =~ /^(80|443|7080|7081)$/ )) { 2261 | our $portXvhost_count = `LANGUAGE=en_GB.UTF-8 $apachectl -S 2>&1 | egrep -v "lists|default|webmail" | grep -c "port $real_port "`; 2262 | chomp ($portXvhost_count); 2263 | if ($portXvhost_count gt 0 ) { 2264 | if ( ! $NOINFO ) { show_info_box(); print " |________ of which ${CYAN}$portXvhost_count${ENDC} are listening on nonstandard port ${CYAN}$real_port${ENDC}.\n" } 2265 | } 2266 | } 2267 | } 2268 | if ($vhost_count >= $maxclients) { 2269 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 2270 | if ( ! $NOWARN ) { show_advisory_box(); print "${YELLOW}Current Apache vHost Count is greater than maxrequestworkers, which is unusual, but can be valid in some scenarios.${ENDC}\n" } 2271 | } else { 2272 | if ( ! $NOWARN ) { show_advisory_box(); print "${YELLOW}Current Apache vHost Count is greater than maxclients, which is unusual, but can be valid in some scenarios.${ENDC}\n" } 2273 | } 2274 | } else { 2275 | if ( our $apache_version =~ m/.*\s*\/2.4.*/) { 2276 | if ( ! $NOOK ) { show_ok_box(); print "Current Apache vHost Count is ${CYAN}less than maxrequestworkers${ENDC}.\n" } 2277 | } else { 2278 | if ( ! $NOOK ) { show_ok_box(); print "Current Apache vHost Count is ${CYAN}less than maxclients${ENDC}.\n" } 2279 | } 2280 | } 2281 | if ( $vhost_count == 0 ) { 2282 | if ( ! $NOWARN ) { show_advisory_box(); print "${YELLOW}vHost Count works only when we have NameVirtualHosting enabled, check config manually, they may only have the default vhost.${ENDC}\n" } 2283 | } 2284 | 2285 | # Check 17 2286 | # show MaxRequestsPerChild (applies only to PreFork model) 2287 | if ( $model eq "prefork") { 2288 | our $maxrequestsperchild = find_master_value(\@config_array, $model, 'MaxRequestsPerChild'); 2289 | if($maxrequestsperchild eq 'CONFIG NOT FOUND') { 2290 | if ( ! $NOWARN ) { show_warn_box; print "MaxRequestsPerChild directive not found.\n" } 2291 | } else { 2292 | if ( ! $NOINFO ) { show_info_box(); print "Your MaxRequestsPerChild setting is ${CYAN}$maxrequestsperchild${ENDC}.\n" } 2293 | } 2294 | # Quick fix for issue #304 - needs adiitional logic for polishing output, for now just report on both. 2295 | our $maxconnectionsperchild = find_master_value(\@config_array, $model, 'MaxConnectionsPerChild'); 2296 | if($maxconnectionsperchild eq 'CONFIG NOT FOUND') { 2297 | if ( ! $NOWARN ) { show_warn_box; print "MaxConnectionsPerChild directive not found.\n" } 2298 | } else { 2299 | if ( ! $NOINFO ) { show_info_box(); print "Your MaxConnectionsPerChild setting is ${CYAN}$maxconnectionsperchild${ENDC}.\n" } 2300 | } 2301 | } 2302 | 2303 | # check #17a-1 detect control panels 2304 | detect_plesk_version(); 2305 | detect_cpanel_version(); 2306 | detect_virtualmin_version(); 2307 | 2308 | # Check 17b 2309 | # Display the php memory limit 2310 | # Note that we do nothing with this in terms of calculations 2311 | # Use it as a conversation starter, esp if memory_limit is 3GB! as this is a per-process setting! 2312 | # get the PHP memory limit 2313 | # This has been abstracted to a separate subroutine 2314 | our $PHP; 2315 | if ($PHP) { 2316 | detect_php_memory_limit(); 2317 | } 2318 | 2319 | # Check 17c : Other Services 2320 | # This has been abstracted out into a separate subroutine 2321 | detect_additional_services(); 2322 | 2323 | # Check 17d : Large Logs in /var/log 2324 | systemcheck_large_logs("/var/log/httpd"); 2325 | systemcheck_large_logs("/var/log/httpd24"); 2326 | systemcheck_large_logs("/var/log/apache2"); 2327 | systemcheck_large_logs("/var/log/php-fpm"); 2328 | systemcheck_large_logs("/usr/local/apache/logs"); 2329 | systemcheck_large_logs("/usr/local/apache2/logs"); 2330 | systemcheck_large_logs("/usr/local/httpd/logs"); 2331 | 2332 | # Check 19 : Maxclients Hits 2333 | # This has been abstracted out into a separate subroutine 2334 | if ( ! $SKIPMAXCLIENTS ) { 2335 | detect_maxclients_hits($model, $process_name) 2336 | } else { 2337 | if ( ! $NOINFO ) { show_advisory_box(); print "Skipping Maxclients Hits check.\n" } 2338 | } 2339 | 2340 | # Check 20 : PHP Fatal Errors 2341 | # This has been abstracted out into a separate subroutine 2342 | # This addresses issue #6 'Check for and report on PHP Fatal Errors in the logs' 2343 | if ( ! $SKIPPHPFATAL ) { 2344 | if ($PHP) { 2345 | detect_php_fatal_errors($model, $process_name); 2346 | } 2347 | } else { 2348 | if ( ! $NOINFO ) { show_advisory_box(); print "Skipping PHP FATAL Errors check.\n" } 2349 | } 2350 | 2351 | # Check 21 : Apache updates 2352 | if ( ! $SKIPUPDATES ) { 2353 | detect_package_updates() 2354 | } else { 2355 | if ( ! $NOINFO ) { show_advisory_box(); print "Skipping Package Updates check.\n" } 2356 | } 2357 | } 2358 | 2359 | sub detect_package_updates { 2360 | my ($distro, $version, $codename) = get_os_platform(); 2361 | our $package_update = 0; 2362 | if (ucfirst($distro) eq "Ubuntu" or ucfirst($distro) eq "Debian" or ucfirst($distro) eq "Debian GNU/Linux") { 2363 | $package_update = `apt-get update 2>&1 >/dev/null && dpkg --get-selections | xargs apt-cache policy | grep -1 Installed | sed -r 's/(:|Installed: |Candidate: )//' | uniq -u | tac | sed '/--/I,+1 d' | tac | sed '\$d' | sed -n 1~2p | egrep "^php|^apache2"`; 2364 | } elsif (ucfirst($distro) eq "SUSE Linux Enterprise Server") { 2365 | $package_update = `zypper list-updates | egrep "^httpd|^php"`; 2366 | } elsif (ucfirst($distro) eq "Bitnami" or 2367 | ucfirst($distro) eq "Bitnami (Debian GNU/Linux)" or 2368 | ucfirst($distro) eq "Gentoo") { 2369 | # we skip package updates for bitnami as it's considered immutable 2370 | show_warn_box(); print "Skipping update checks for Bitnami and Gentoo.\n"; 2371 | return 0; 2372 | } else { 2373 | $package_update = `yum check-update | egrep "^httpd|^php"`; 2374 | } 2375 | if ($package_update) { 2376 | if ( $package_update !~ /Failed|Error/) { 2377 | if ( ! $NOWARN ) { 2378 | show_crit_box(); print "${RED}Apache and / or PHP has a pending package update available.${ENDC}\n"; 2379 | print "${YELLOW}$package_update${ENDC}"; 2380 | } else { 2381 | show_crit_box(); print "${RED}There was an error getting package updates, please check your package manager for potential problems, and try again.${ENDC}\n"; 2382 | show_crit_box(); print "${RED} - Or use ${CYAN}--skip-updates${ENDC}\n"; 2383 | exit 1; 2384 | } 2385 | } 2386 | } else { 2387 | if (-d "/usr/local/httpd" or -d "/usr/local/apache" or -d "/usr/local/apache2") { 2388 | if ( ! $NOWARN ) { show_warn_box(); print "${RED}It looks like apache was installed from sources. Skipping update checks.${ENDC}\n" } 2389 | } else { 2390 | if ( ! $NOOK ) { show_ok_box(); print "${GREEN}No package updates found.${ENDC}\n" } 2391 | } 2392 | } 2393 | 2394 | } 2395 | 2396 | sub detect_cpanel_version { 2397 | our $cpanel = 0; 2398 | our $cpanel = 1 if -d "/usr/local/cpanel"; 2399 | if ($cpanel) { 2400 | my $cpanel_version = 0; 2401 | $cpanel_version = `cat /usr/local/cpanel/version` if (-f "/usr/local/cpanel/version"); 2402 | chomp($cpanel_version); 2403 | if ($cpanel_version) { 2404 | if ( ! $NOINFO ) { show_info_box(); print "cPanel Version: ${CYAN}$cpanel_version${ENDC}\n" } 2405 | } else { 2406 | if ( ! $NOINFO ) { show_info_box(); print "cPanel Version: ${CYAN}NOT FOUND${ENDC}\n" } 2407 | } 2408 | 2409 | } else { 2410 | if ( ! $NOINFO ) { show_info_box(); print "This server is NOT running cPanel.\n" } 2411 | } 2412 | } 2413 | 2414 | sub detect_plesk_version { 2415 | our $plesk = 0; 2416 | our $plesk = 1 if -d "/usr/local/psa"; 2417 | if ($plesk) { 2418 | my $plesk_version = 0; 2419 | $plesk_version = `cat /usr/local/psa/version` if (-f "/usr/local/psa/version"); 2420 | chomp($plesk_version); 2421 | if ($plesk_version) { 2422 | if ( ! $NOINFO ) { show_info_box(); print "Plesk Version: ${CYAN}$plesk_version${ENDC}\n" } 2423 | } else { 2424 | if ( ! $NOINFO ) { show_info_box(); print "Plesk Version: ${CYAN}NOT FOUND${ENDC}\n" } 2425 | } 2426 | 2427 | } else { 2428 | if ( ! $NOINFO ) { show_info_box(); print "This server is NOT running Plesk.\n" } 2429 | } 2430 | } 2431 | 2432 | sub detect_virtualmin_version { 2433 | our $vmin = 0; 2434 | our $vmin = 1 if -f "/usr/sbin/virtualmin"; 2435 | if ($vmin) { 2436 | my $vmin_version = 0; 2437 | $vmin_version = `/usr/sbin/virtualmin info | grep "virtualmin version" | awk -F":" '{ print \$2}'`; 2438 | chomp($vmin_version); 2439 | my $wmin_version = 0; 2440 | $wmin_version = `/usr/sbin/virtualmin info | grep "webmin version" | awk -F":" '{ print \$2}'`; 2441 | chomp($wmin_version); 2442 | if ( ! $NOINFO ) { show_info_box(); print "Virtualmin Version: ${CYAN}$vmin_version${ENDC}\n" } 2443 | if ( ! $NOINFO ) { show_info_box(); print "Webmin Version: ${CYAN}$wmin_version${ENDC}\n" } 2444 | } else { 2445 | if ( ! $NOINFO ) { show_info_box(); print "This server is NOT running Virtualmin.\n" } 2446 | } 2447 | } 2448 | 2449 | sub detect_php_fatal_errors { 2450 | our ($model, $process_name) = @_; 2451 | 2452 | # quit early if bitnami, as those logs go to stdout and are no good for us 2453 | if ($process_name eq "/opt/bitnami/apache/bin/httpd" ) { 2454 | show_warn_box(); print "Skipping checking logs for PHP Fatal Errors, Bitnami sends these to stdout.\n"; 2455 | return 0; 2456 | } 2457 | 2458 | print "VERBOSE: Checking logs for PHP Fatal Errors, this can take some time...\n" if $main::VERBOSE; 2459 | our $phpfpm_detected; 2460 | 2461 | # we can only check apache logs for PHP errors in prefork mode 2462 | if (! $model eq "prefork") { 2463 | show_warn_box(); print "Skipping checking logs for PHP Fatal Errors, we can only do this if apache is running in prefork and with mod_php running under apache.\n"; 2464 | return; 2465 | } 2466 | 2467 | if ($process_name eq "/usr/sbin/httpd" ) { 2468 | our $SCANDIR = "/var/log/httpd/"; 2469 | } elsif ($process_name eq "/opt/rh/httpd24/root/usr/sbin/httpd" ) { 2470 | our $SCANDIR = "/var/log/httpd24/"; 2471 | } elsif ($process_name eq "/usr/local/apache/bin/httpd" ) { 2472 | our $SCANDIR = "/usr/local/apache/logs/"; 2473 | } else { 2474 | our $SCANDIR = "/var/log/apache2/"; 2475 | } 2476 | our $SCANDIR; 2477 | our %logfile_counts; 2478 | grep_php_fatal($SCANDIR); 2479 | 2480 | if ($phpfpm_detected) { 2481 | our $SCANDIR = "/var/log/php-fpm/"; 2482 | our %logfile_counts; 2483 | grep_php_fatal($SCANDIR); 2484 | } 2485 | our %logfile_counts; 2486 | if (%logfile_counts) { 2487 | if ( ! $NOWARN ) { 2488 | show_crit_box(); 2489 | print "${RED}PHP Fatal errors were found, see summaries below.${ENDC}\n"; 2490 | show_advisory_box(); print "${YELLOW}Check the logs manually.${ENDC}\n"; 2491 | while( my( $key, $value ) = each %logfile_counts ){ 2492 | show_advisory_box(); print " - ${YELLOW}$key${ENDC}: ${CYAN}$value${ENDC}\n"; 2493 | } 2494 | our $plesk; 2495 | if ($plesk) { 2496 | print "\n"; 2497 | show_warn_box(); print "${YELLOW}Note: Plesk logs are NOT checked, as 100+ domains would generate 500+ lines of output, this is too much, so please check this manually:${ENDC}\n"; 2498 | show_advisory_box(); print "Use this command to check ALL domains: ${CYAN}grep -Hi fatal /var/www/vhosts/*/statistics/logs/*${ENDC}\n"; 2499 | show_advisory_box(); print "Use this command to check ONLY \"example.com\": ${CYAN}grep -Hi fatal /var/www/vhosts/example.com/statistics/logs/*${ENDC}\n"; 2500 | } 2501 | } 2502 | } else { 2503 | if ( ! $NOOK ) { 2504 | show_ok_box(); 2505 | print "${GREEN}No PHP Fatal Errors were found.${ENDC}\n"; 2506 | return; 2507 | } 2508 | } 2509 | } 2510 | 2511 | 2512 | sub grep_php_fatal { 2513 | my ($SCANDIR) = @_; 2514 | our %logfile_counts; 2515 | my @logfile_list; 2516 | find(sub {push @logfile_list, $File::Find::name if ( -f $_ ) }, $SCANDIR); 2517 | foreach my $file (@logfile_list) { 2518 | our $phpfatalerror_hits = 0; 2519 | open(FILE, $file); 2520 | while () { 2521 | $phpfatalerror_hits++ if $_ =~ /php fatal/i; 2522 | } 2523 | close(FILE); 2524 | if ($phpfatalerror_hits) { $logfile_counts{ $file } = $phpfatalerror_hits } 2525 | } 2526 | } 2527 | 2528 | 2529 | sub detect_maxclients_hits { 2530 | our ($model, $process_name) = @_; 2531 | # quit early if bitnami, as those logs go to stdout and are no good for us 2532 | if ($process_name eq "/opt/bitnami/apache/bin/httpd" ) { 2533 | show_warn_box(); print "Skipping checking logs for MaxClients/MaxRequestWorkers hits, Bitnami sends these to stdout.\n"; 2534 | return 0; 2535 | } 2536 | # we can only check apache logs for PHP errors in prefork mode 2537 | if ( ! $model eq "prefork") { 2538 | show_warn_box(); print "Skipping checking logs for MaxClients/MaxRequestWorkers Hits, we can only do this if apache is running in prefork.\n"; 2539 | return; 2540 | } 2541 | our $hit = 0; 2542 | if ($process_name eq "/usr/sbin/httpd") { 2543 | our $maxclients_hits = `grep -i reached /var/log/httpd/error_log | egrep -v "mod" | tail -5`; 2544 | } elsif ($process_name eq "/opt/rh/httpd24/root/usr/sbin/httpd") { 2545 | our $maxclients_hits = `grep -i reached /var/log/httpd24/error_log | egrep -v "mod" | tail -5`; 2546 | } elsif ($process_name eq "/usr/local/apache/bin/httpd") { 2547 | our $maxclients_hits = `grep -i reached /usr/local/apache/logs/error_log | egrep -v "mod" | tail -5`; 2548 | } elsif ($process_name eq "/opt/apache2/bin/httpd") { 2549 | our $maxclients_hits = `find /opt/apache2/logs -name "error*" | tail -1 | xargs grep -i reached | egrep -v "mod" | tail -5`; 2550 | } elsif ($process_name eq "/usr/sbin/httpd-prefork") { 2551 | our $maxclients_hits = `find /var/log/apache2 -name "error*" | tail -1 | xargs grep -i reached | egrep -v "mod" | tail -5`; 2552 | } else { 2553 | # general ToDo would be `'grep "^ErrorLog " $apache_conf_file'`; 2554 | # to get configuration like: 2555 | # ErrorLog "|/opt/apache2/bin/rotatelogs /opt/apache2/logs/error.log.%Y-%m-%d 86400" 2556 | # and finally extract the ErrorLog file location for further processing 2557 | # `'find /opt/apache2/logs -name "error*" | tail -1'` 2558 | our $maxclients_hits = `grep -i reached /var/log/apache2/error.log | egrep -v "mod" | tail -5`; 2559 | } 2560 | our $maxclients_hits; 2561 | if ($maxclients_hits) { 2562 | $hit = 1; 2563 | } 2564 | our $hit; 2565 | if ($hit) { 2566 | if ( ! $NOWARN ) { 2567 | show_warn_box(); 2568 | print "${YELLOW}MaxClients has been hit recently (maximum of 5 results shown), consider the dates and times below:${ENDC}\n"; 2569 | print $maxclients_hits; 2570 | } 2571 | } else { 2572 | if ( ! $NOOK ) { 2573 | show_ok_box(); 2574 | print "${GREEN}MaxClients has not been hit recently.${ENDC}\n"; 2575 | if ( ! $NOWARN ) { 2576 | show_warn_box(); 2577 | print "${YELLOW}Apache only logs maxclients/maxrequestworkers hits once in a lifetime, if no restart has happened this event may have been rotated away.${ENDC}\n"; 2578 | show_warn_box(); 2579 | print "${YELLOW}As a backup check, please compare number of running apache processes (minus 1 for parent) against maxclients/maxrequestworkers.${ENDC}\n"; 2580 | show_warn_box(); 2581 | print "${YELLOW}For more information see ${CYAN}https://github.com/apache/httpd/blob/0b61edca6cdda2737aa1d84a4526c5f9d2e23a8c/server/mpm/prefork/prefork.c#L809${ENDC}\n"; 2582 | } 2583 | return; 2584 | } 2585 | } 2586 | 2587 | } 2588 | 2589 | 2590 | sub detect_php_memory_limit { 2591 | if ( ! $NOINFO) { 2592 | my $php_exe = `which php`; 2593 | chomp ($php_exe); 2594 | our $apache_proc_php_mem_limit = get_php_setting($php_exe, 'memory_limit'); 2595 | show_info_box(); print "Your PHP Memory Limit (Per-Process) is ${CYAN}".$apache_proc_php_mem_limit."${ENDC}.\n"; 2596 | if ($apache_proc_php_mem_limit eq "-1") { 2597 | show_advisory_box(); print "You should set a PHP Memory Limit (-1 is ${CYAN}UNLIMITED${ENDC}) which is not recommended.\n"; 2598 | } 2599 | } 2600 | } 2601 | 2602 | sub get_service_memory_usage_mbytes { 2603 | my ( $svc ) = @_; 2604 | if ($svc eq "varnishd") { 2605 | # we have to treat varnish somewhat differently due to changes made in 4.1+ 2606 | my $vcache_detected = 0; 2607 | # check for the existence of a 'vcache' user, which is the default user of varnish after 4.1 2608 | # checking this way prevents ugly errors 2609 | $vcache_detected = getpwnam("vcache"); 2610 | if ( $vcache_detected ) { 2611 | my @usage_by_pids = `ps -U vcache -C varnishd -o rss | grep -v RSS`; 2612 | our $usage_mbytes = 0; 2613 | foreach my $proc (@usage_by_pids) { 2614 | our $usage_mbytes += $proc / 1024; 2615 | } 2616 | our $usage_mbytes = round($usage_mbytes); 2617 | return $usage_mbytes; 2618 | } else { 2619 | my @usage_by_pids = `ps -C varnishd -o rss | grep -v RSS`; 2620 | our $usage_mbytes = 0; 2621 | foreach my $proc (@usage_by_pids) { 2622 | our $usage_mbytes += $proc / 1024; 2623 | } 2624 | our $usage_mbytes = round($usage_mbytes); 2625 | return $usage_mbytes; 2626 | } 2627 | } else { 2628 | my @usage_by_pids = `ps -C $svc -o rss | grep -v RSS`; 2629 | our $usage_mbytes = 0; 2630 | foreach my $proc (@usage_by_pids) { 2631 | our $usage_mbytes += $proc / 1024; 2632 | } 2633 | our $usage_mbytes = round($usage_mbytes); 2634 | return $usage_mbytes; 2635 | } 2636 | } 2637 | 2638 | 2639 | sub detect_additional_services { 2640 | if ($VERBOSE) { print "VERBOSE: Begin detecting additional services...\n" } 2641 | our $servicefound_flag = 0; # we need this to give a message if nothing was found, otherwise it looks silly. 2642 | # Detect Mysql 2643 | our $mysql_detected = 0; 2644 | our $mysql_detected = `ps -C mysqld -o rss | grep -v RSS`; 2645 | if ( $mysql_detected ) { 2646 | if ($VERBOSE) { print "VERBOSE: MySQL Detected\n" } 2647 | our $servicefound_flag = 1; 2648 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}MySQL${ENDC} Detected => " } 2649 | # Get MySQL Memory Usage 2650 | our $mysql_memory_usage_mbytes = get_service_memory_usage_mbytes("mysqld"); 2651 | if ( ! $NOINFO ) { print "Using ${CYAN}$mysql_memory_usage_mbytes MB${ENDC} of memory.\n" } 2652 | } else { 2653 | if ($VERBOSE) { print "VERBOSE: MySQL NOT Detected\n" } 2654 | our $mysql_memory_usage_mbytes = 0; 2655 | } 2656 | 2657 | # Detect Java 2658 | our $java_detected = 0; 2659 | $java_detected = `ps -C java -o rss | grep -v RSS`; 2660 | if ( $java_detected ) { 2661 | if ($VERBOSE) { print "VERBOSE: Java Detected\n" } 2662 | our $servicefound_flag = 1; 2663 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}Java${ENDC} Detected => " } 2664 | our $java_memory_usage_mbytes = get_service_memory_usage_mbytes("java"); 2665 | if ( ! $NOINFO ) { print "Using ${CYAN}$java_memory_usage_mbytes MB${ENDC} of memory.\n" } 2666 | } else { 2667 | if ($VERBOSE) { print "VERBOSE: Java NOT Detected\n" } 2668 | our $java_memory_usage_mbytes = 0; 2669 | } 2670 | 2671 | # Detect Varnish 2672 | our $varnish_detected = 0; 2673 | $varnish_detected = `ps -C varnishd -o rss | grep -v RSS`; 2674 | if ( $varnish_detected ) { 2675 | if ($VERBOSE) { print "VERBOSE: Varnish Detected\n" } 2676 | our $servicefound_flag = 1; 2677 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}Varnish${ENDC} Detected => " } 2678 | # Get varnish Memory Usage 2679 | our $varnish_memory_usage_mbytes = get_service_memory_usage_mbytes("varnishd"); 2680 | if ( ! $NOINFO ) { print "Using ${CYAN}$varnish_memory_usage_mbytes MB${ENDC} of memory.\n" } 2681 | } else { 2682 | if ($VERBOSE) { print "VERBOSE: Varnish NOT Detected\n" } 2683 | our $varnish_memory_usage_mbytes = 0; 2684 | } 2685 | 2686 | # Detect Redis 2687 | our $redis_detected = 0; 2688 | $redis_detected = `ps -C redis-server -o rss | grep -v RSS`; 2689 | if ( $redis_detected ) { 2690 | if ($VERBOSE) { print "VERBOSE: Redis Detected\n" } 2691 | our $servicefound_flag = 1; 2692 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}Redis${ENDC} Detected => " } 2693 | # Get Redis Memory Usage 2694 | our $redis_memory_usage_mbytes = get_service_memory_usage_mbytes("redis-server"); 2695 | if ( ! $NOINFO ) { print "Using ${CYAN}$redis_memory_usage_mbytes MB${ENDC} of memory.\n" } 2696 | } else { 2697 | if ($VERBOSE) { print "VERBOSE: Redis NOT Detected\n" } 2698 | our $redis_memory_usage_mbytes = 0; 2699 | } 2700 | 2701 | # Detect Memcache 2702 | our $memcache_detected = 0; 2703 | $memcache_detected = `ps -C memcached -o rss | grep -v RSS`; 2704 | if ( $memcache_detected ) { 2705 | if ($VERBOSE) { print "VERBOSE: Memcache Detected\n" } 2706 | our $servicefound_flag = 1; 2707 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}Memcache${ENDC} Detected => " } 2708 | # Get Memcache Memory Usage 2709 | our $memcache_memory_usage_mbytes = get_service_memory_usage_mbytes("memcached"); 2710 | if ( ! $NOINFO ) { print "Using ${CYAN}$memcache_memory_usage_mbytes MB${ENDC} of memory.\n" } 2711 | } else { 2712 | if ($VERBOSE) { print "VERBOSE: Memcache NOT Detected\n" } 2713 | our $memcache_memory_usage_mbytes = 0; 2714 | } 2715 | 2716 | # Detect PHP-FPM 2717 | our $phpfpm_detected = 0; 2718 | # Get PHP-FPM Memory Usage 2719 | $phpfpm_detected = `ps -C php-fpm -o rss | grep -v RSS` || `ps -C php5-fpm -o rss | grep -v RSS` || 0; 2720 | if ( $phpfpm_detected ) { 2721 | if ($VERBOSE) { print "VERBOSE: PHP-FPM Detected\n" } 2722 | our $servicefound_flag = 1; 2723 | # Get PHP-FPM Memory Usage 2724 | our $phpfpm = 0; 2725 | our $phpfpm = `ps -C php-fpm -o rss | grep -v RSS`; 2726 | our $php5fpm = 0; 2727 | our $php5fpm = `ps -C php5-fpm -o rss | grep -v RSS`; 2728 | if ( $phpfpm ) { 2729 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}PHP-FPM${ENDC} Detected => " } 2730 | our $phpfpm_memory_usage_mbytes = get_service_memory_usage_mbytes("php-fpm"); 2731 | } elsif ( $php5fpm ) { 2732 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}PHP5-FPM${ENDC} Detected => " } 2733 | our $phpfpm_memory_usage_mbytes = get_service_memory_usage_mbytes("php5-fpm"); 2734 | } 2735 | our $phpfpm_memory_usage_mbytes; 2736 | if ( ! $NOINFO ) { print "Using ${CYAN}$phpfpm_memory_usage_mbytes MB${ENDC} of memory.\n" } 2737 | } else { 2738 | if ($VERBOSE) { print "VERBOSE: PHP-FPM NOT Detected\n" } 2739 | our $phpfpm_memory_usage_mbytes = 0; 2740 | } 2741 | 2742 | # Detect Gluster 2743 | our $gluster_detected = 0; 2744 | $gluster_detected = `ps -C glusterd -o rss | grep -v RSS`; 2745 | if ( $gluster_detected ) { 2746 | if ($VERBOSE) { print "VERBOSE: Gluster Detected\n" } 2747 | our $servicefound_flag = 1; 2748 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}Gluster${ENDC} Detected => " } 2749 | # Get Gluster Memory Usage 2750 | our $glusterd_memory_usage_mbytes = get_service_memory_usage_mbytes("glusterd"); 2751 | our $glusterfs_memory_usage_mbytes = get_service_memory_usage_mbytes("glusterfs"); 2752 | our $glusterfsd_memory_usage_mbytes = get_service_memory_usage_mbytes("glusterfsd"); 2753 | our $gluster_memory_usage_mbytes = $glusterd_memory_usage_mbytes + $glusterfs_memory_usage_mbytes + $glusterfsd_memory_usage_mbytes; 2754 | if ( ! $NOINFO ) { print "Using ${CYAN}$gluster_memory_usage_mbytes MB${ENDC} of memory.\n" } 2755 | } else { 2756 | if ($VERBOSE) { print "VERBOSE: Gluster NOT Detected\n" } 2757 | our $gluster_memory_usage_mbytes = 0; 2758 | } 2759 | if ( $servicefound_flag == 0 ) { 2760 | if ( ! $NOOK ) { show_ok_box(); print "${GREEN}No additional services were detected.${ENDC}\n" } 2761 | } else { 2762 | print "\n"; # add a aseparator before the next section 2763 | } 2764 | if ($VERBOSE) { print "VERBOSE: End detecting additional services...\n" } 2765 | } 2766 | 2767 | 2768 | sub get_hostname { 2769 | our $hostname = `which hostname`; 2770 | chomp($hostname); 2771 | if ( $hostname eq '' ) { 2772 | show_crit_box(); 2773 | print "Cannot find the 'hostname' executable."; 2774 | exit 1; 2775 | } else { 2776 | our $servername = `$hostname -f`; 2777 | chomp($servername); 2778 | return $servername; 2779 | } 2780 | } 2781 | 2782 | sub get_ip { 2783 | our $curl = `which curl`; 2784 | chomp ($curl); 2785 | if ( $curl eq '' ) { 2786 | show_crit_box; 2787 | print "Cannot find the 'curl' executable."; 2788 | exit 1; 2789 | } else { 2790 | # create an array of possible providers 2791 | # https://linuxconfig.org/how-to-use-curl-to-get-public-ip-address 2792 | my @ip_providers = ('myip.dnsomatic.com', 2793 | 'ipv4.icanhazip.com', 2794 | 'ifconfig.me', 2795 | 'api.ipify.org', 2796 | 'bot.whatismyipaddress.com', 2797 | 'ipinfo.io/ip', 2798 | 'ipecho.net/plain'); 2799 | our $ip = ''; 2800 | my $max_tries = scalar @ip_providers; 2801 | my %tried; 2802 | 2803 | # Randomise the selection 2804 | while ($max_tries--) { 2805 | # Pick a random provider that hasn't been tried yet 2806 | my @remaining = grep { !$tried{$_} } @ip_providers; 2807 | last unless @remaining; # safety check 2808 | 2809 | my $ip_provider = $remaining[ rand @remaining ]; 2810 | $tried{$ip_provider} = 1; 2811 | 2812 | $ip = `$curl -s $ip_provider`; 2813 | chomp($ip); 2814 | 2815 | # Basic validation: is it an IP address and not an error page? 2816 | if ($ip =~ /^(\d{1,3}\.){3}\d{1,3}$/) { 2817 | return $ip; 2818 | } 2819 | } 2820 | # this is now much less likely to return a x.x.x.x now ee have an 2821 | # array of providers, rather than relying on one provider alone 2822 | return "x.x.x.x" # fallback only if no providers returned an IP 2823 | } 2824 | } 2825 | 2826 | ######################## 2827 | # BEGIN MAIN EXECUTION # 2828 | ######################## 2829 | 2830 | # if the user has added the help flag, or if they have defined a port 2831 | if ( $help eq 1 || $port eq 0 ) { 2832 | usage(); 2833 | exit; 2834 | } 2835 | 2836 | # print the header 2837 | my $hn = get_hostname(); 2838 | my $ip = get_ip(); 2839 | print_header($hn, $ip); 2840 | 2841 | # do the preflight checks 2842 | preflight_checks(); 2843 | 2844 | our $hostname; 2845 | our $public_ip_address; 2846 | our @config_array; 2847 | our $apache_user_running; 2848 | our $apache_user_config; 2849 | our $model; 2850 | our @apache_uptime; 2851 | if ( ! $NOCHKPID) { 2852 | our $uptime = "$apache_uptime[0]d $apache_uptime[1]h $apache_uptime[2]m $apache_uptime[3]s"; 2853 | } else { 2854 | our $uptime = "SKIPPED"; 2855 | } 2856 | our $uptime; 2857 | our $process_name; 2858 | our $available_mem; 2859 | our $maxclients; 2860 | our $vhost_count; 2861 | our $flag_trigger = 0; 2862 | our $threadsperchild; 2863 | our $serverlimit; 2864 | our $mysql_detected; 2865 | our $mysql_memory_usage_mbytes; 2866 | our $java_detected; 2867 | our $java_memory_usage_mbytes; 2868 | our $redis_detected; 2869 | our $redis_memory_usage_mbytes; 2870 | our $memcache_detected; 2871 | our $memcache_memory_usage_mbytes; 2872 | our $varnish_detected; 2873 | our $varnish_memory_usage_mbytes; 2874 | our $phpfpm_detected; 2875 | our $phpfpm_memory_usage_mbytes; 2876 | our $gluster_memory_usage_mbytes; 2877 | 2878 | # Detect httpd 2879 | our $httpd_detected = 0; 2880 | our $httpd_detected = `ps -C httpd -o rss | grep -v RSS`; 2881 | if ( $httpd_detected ) { 2882 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}httpd${ENDC} " } 2883 | # Get httpd Memory Usage 2884 | our $httpd_memory_usage_mbytes = get_service_memory_usage_mbytes("httpd"); 2885 | if ( ! $NOINFO ) { print "is currently using ${CYAN}$httpd_memory_usage_mbytes MB${ENDC} of memory.\n" } 2886 | } else { 2887 | our $httpd_memory_usage_mbytes = 0; 2888 | } 2889 | # Detect apache2 2890 | our $apache2_detected = 0; 2891 | our $apache2_detected = `ps -C apache2 -o rss | grep -v RSS`; 2892 | if ( $apache2_detected ) { 2893 | if ( ! $NOINFO ) { show_info_box(); print "${CYAN}apache2${ENDC} " } 2894 | # Get apache2 Memory Usage 2895 | our $apache2_memory_usage_mbytes = get_service_memory_usage_mbytes("apache2"); 2896 | if ( ! $NOINFO ) { print "is currently using ${CYAN}$apache2_memory_usage_mbytes MB${ENDC} of memory.\n" } 2897 | } else { 2898 | our $apache2_memory_usage_mbytes = 0; 2899 | } 2900 | 2901 | our $apache_user_running; 2902 | my $apache_proc_highest = get_memory_usage($process_name, $apache_user_running, 'high'); 2903 | my $apache_proc_lowest = get_memory_usage($process_name, $apache_user_running, 'low'); 2904 | my $apache_proc_average = get_memory_usage($process_name, $apache_user_running, 'average'); 2905 | 2906 | 2907 | if ( $model eq "prefork") { 2908 | if ( ! $NOINFO ) { show_info_box(); print "The smallest apache process is using ${CYAN}$apache_proc_lowest MB${ENDC} of memory\n" } 2909 | if ( ! $NOINFO ) { show_info_box(); print "The average apache process is using ${CYAN}$apache_proc_average MB${ENDC} of memory\n" } 2910 | if ( ! $NOINFO ) { show_info_box(); print "The largest apache process is using ${CYAN}$apache_proc_highest MB${ENDC} of memory\n" } 2911 | 2912 | my $average_potential_use = $maxclients * $apache_proc_average; 2913 | $average_potential_use = round($average_potential_use); 2914 | my $average_potential_use_pct = round(($average_potential_use/$available_mem)*100); 2915 | # Calculate percentages of remaining RAM, after services considered: 2916 | print "VERBOSE: Available Mem: $available_mem\n" if $main::VERBOSE; 2917 | print "VERBOSE: Mysql Mem: $mysql_memory_usage_mbytes\n" if $main::VERBOSE; 2918 | print "VERBOSE: Java Mem: $java_memory_usage_mbytes\n" if $main::VERBOSE; 2919 | print "VERBOSE: Redis Mem: $redis_memory_usage_mbytes\n" if $main::VERBOSE; 2920 | print "VERBOSE: Memcache Mem: $memcache_memory_usage_mbytes\n" if $main::VERBOSE; 2921 | print "VERBOSE: Varnish Mem: $varnish_memory_usage_mbytes\n" if $main::VERBOSE; 2922 | print "VERBOSE: PHP-FPM Mem: $phpfpm_memory_usage_mbytes\n" if $main::VERBOSE; 2923 | print "VERBOSE: Gluster Mem: $gluster_memory_usage_mbytes\n" if $main::VERBOSE; 2924 | my $memory_remaining = $available_mem - $mysql_memory_usage_mbytes - $java_memory_usage_mbytes - $redis_memory_usage_mbytes - 2925 | $memcache_memory_usage_mbytes - $varnish_memory_usage_mbytes - $phpfpm_memory_usage_mbytes - $gluster_memory_usage_mbytes; 2926 | print "VERBOSE: Average Potential Use : $average_potential_use\n" if $main::VERBOSE; 2927 | print "VERBOSE: Mem Remaining: $memory_remaining\n" if $main::VERBOSE; 2928 | if ($memory_remaining < 0) { 2929 | show_crit_box(); print "${RED}ERROR: Memory Overload Error: Remaining RAM in negative numbers! Dumping memory report, and exiting...${ENDC}\n"; 2930 | print "Available Mem: $available_mem\n"; 2931 | print "----------------------------------------------\n"; 2932 | print "Mysql Mem: $mysql_memory_usage_mbytes\n"; 2933 | print "Java Mem: $java_memory_usage_mbytes\n"; 2934 | print "Redis Mem: $redis_memory_usage_mbytes\n"; 2935 | print "Memcache Mem: $memcache_memory_usage_mbytes\n"; 2936 | print "Varnish Mem: $varnish_memory_usage_mbytes\n"; 2937 | print "PHP-FPM Mem: $phpfpm_memory_usage_mbytes\n"; 2938 | print "Gluster Mem: $gluster_memory_usage_mbytes\n"; 2939 | print "----------------------------------------------\n"; 2940 | print "Remaining Mem: $memory_remaining\n"; 2941 | exit 1; 2942 | } 2943 | my $average_potential_use_pct_remain = round(($average_potential_use/$memory_remaining)*100); 2944 | if ( $average_potential_use_pct > 100 or $average_potential_use_pct_remain > 100 ) { 2945 | show_crit_box(); 2946 | print "Going by the average Apache process, Apache can potentially use ${RED}$average_potential_use MB${ENDC} RAM:\n"; 2947 | if ( $average_potential_use_pct > 100 ) { 2948 | print "\t\tWithout considering services: ${RED}$average_potential_use_pct \%${ENDC} of total installed RAM\n"; 2949 | } else { 2950 | print "\t\tWithout considering services: ${CYAN}$average_potential_use_pct \%${ENDC} of total installed RAM\n"; 2951 | } 2952 | if ( $average_potential_use_pct_remain > 100 ) { 2953 | print "\t\tConsidering extra services: ${RED}$average_potential_use_pct_remain \%${ENDC} of remaining RAM\n"; 2954 | } else { 2955 | print "\t\tConsidering extra services: ${CYAN}$average_potential_use_pct_remain \%${ENDC} of remaining RAM\n"; 2956 | } 2957 | } else { 2958 | if ( ! $NOOK ) { 2959 | show_ok_box(); 2960 | print "Going by the average Apache process, Apache can potentially use ${CYAN}$average_potential_use MB${ENDC} RAM:\n" . 2961 | "\t\tWithout considering services: ${CYAN}$average_potential_use_pct \%${ENDC} of total installed RAM\n" . 2962 | "\t\tConsidering extra services: ${CYAN}$average_potential_use_pct_remain \%${ENDC} of remaining RAM\n"; 2963 | } 2964 | } 2965 | 2966 | my $highest_potential_use = $maxclients * $apache_proc_highest; 2967 | $highest_potential_use = round($highest_potential_use); 2968 | my $highest_potential_use_pct = round(($highest_potential_use/$available_mem)*100); 2969 | # Calculate percentages of remaining RAM, after services considered: 2970 | print "VERBOSE: Highest Potential Use : $highest_potential_use\n" if $main::VERBOSE; 2971 | print "VERBOSE: Mem Remaining: $memory_remaining\n" if $main::VERBOSE; 2972 | 2973 | my $highest_potential_use_pct_remain = round(($highest_potential_use/$memory_remaining)*100); 2974 | if ( $highest_potential_use_pct > 100 or $highest_potential_use_pct_remain > 100 ) { 2975 | show_crit_box(); 2976 | print "Going by the largest Apache process, Apache can potentially use ${RED}$highest_potential_use MB${ENDC} RAM:\n"; 2977 | if ( $highest_potential_use_pct > 100 ) { 2978 | print "\t\tWithout considering services: ${RED}$highest_potential_use_pct \%${ENDC} of total installed RAM\n"; 2979 | } else { 2980 | print "\t\tWithout considering services: ${CYAN}$highest_potential_use_pct \%${ENDC} of total installed RAM\n"; 2981 | } 2982 | if ( $highest_potential_use_pct_remain > 100 ) { 2983 | print "\t\tConsidering extra services: ${RED}$highest_potential_use_pct_remain \%${ENDC} of remaining RAM\n"; 2984 | } else { 2985 | print "\t\tConsidering extra services: ${CYAN}$highest_potential_use_pct_remain \%${ENDC} of remaining RAM\n"; 2986 | } 2987 | } else { 2988 | if ( ! $NOOK ) { 2989 | show_ok_box(); 2990 | print "Going by the largest Apache process, Apache can potentially use ${CYAN}$highest_potential_use MB${ENDC} RAM:\n" . 2991 | "\t\tWithout considering services: ${CYAN}$highest_potential_use_pct %${ENDC} of total installed RAM\n" . 2992 | "\t\tConsidering extra services: ${CYAN}$highest_potential_use_pct_remain %${ENDC} of remaining RAM\n"; 2993 | } 2994 | } 2995 | } 2996 | if ( $model eq "worker") { 2997 | if ( ! $NOINFO ) { show_info_box(); print "The largest apache process is using ${CYAN}$apache_proc_highest MB${ENDC} of memory\n" } 2998 | if ( ! $NOINFO ) { show_info_box(); print "The smallest apache process is using ${CYAN}$apache_proc_lowest MB${ENDC} of memory\n" } 2999 | if ( ! $NOINFO ) { show_info_box(); print "The average apache process is using ${CYAN}$apache_proc_average MB${ENDC} of memory\n" } 3000 | } 3001 | 3002 | generate_standard_report($available_mem, $maxclients, $vhost_count, $apache_proc_lowest, $apache_proc_average, $apache_proc_highest, $model, $uptime, $threadsperchild, $mysql_memory_usage_mbytes, $java_memory_usage_mbytes, $redis_memory_usage_mbytes, $memcache_memory_usage_mbytes, $varnish_memory_usage_mbytes, $phpfpm_memory_usage_mbytes, $gluster_memory_usage_mbytes); 3003 | 3004 | show_important_message(); 3005 | --------------------------------------------------------------------------------