├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── php-cs-fixer.yml │ ├── run-tests.yml │ └── update-changelog.yml ├── .php-cs-fixer.cache ├── .php_cs.dist.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── server-monitor.php ├── database └── migrations │ ├── create_checks_table.php.stub │ └── create_hosts_table.php.stub ├── docs ├── _index.md ├── about-us.md ├── advanced-usage │ ├── _index.md │ ├── customizing-notifications.md │ ├── manipulating-processes.md │ ├── manually-configure-hosts-and-checks.md │ └── using-your-own-model.md ├── changelog.md ├── high-level-overview.md ├── images │ ├── add-host.jpg │ ├── authenticity.jpg │ ├── check-failed.jpg │ ├── check-restored.jpg │ ├── check-succeeded.jpg │ ├── check-warning.jpg │ ├── header.jpg │ ├── list-checks.jpg │ ├── list-hosts.jpg │ └── nginx.jpg ├── installation-and-setup.md ├── introduction.md ├── monitoring-basics │ ├── _index.md │ ├── built-in-checks.md │ ├── listing-hosts-and-checks.md │ ├── managing-hosts.md │ ├── notifications-and-events.md │ └── writing-your-own-checks.md ├── postcardware.md ├── questions-and-issues.md ├── requirements.md └── using-the-stand-alone-version.md └── src ├── CheckCollection.php ├── CheckDefinitions ├── CheckDefinition.php ├── Diskspace.php ├── Elasticsearch.php ├── Memcached.php └── MySql.php ├── CheckRepository.php ├── Commands ├── AddHost.php ├── BaseCommand.php ├── DeleteHost.php ├── DumpChecks.php ├── ListChecks.php ├── ListHosts.php ├── RunChecks.php └── SyncFile.php ├── Events ├── CheckFailed.php ├── CheckRestored.php ├── CheckSucceeded.php ├── CheckWarning.php └── Event.php ├── Exceptions ├── InvalidCheckDefinition.php └── InvalidConfiguration.php ├── Helpers └── ConsoleOutput.php ├── HostRepository.php ├── Manipulators ├── Manipulator.php └── Passthrough.php ├── Models ├── Check.php ├── Concerns │ ├── HandlesCheckResult.php │ ├── HasCustomProperties.php │ ├── HasProcess.php │ └── ThrottlesFailingNotifications.php ├── Enums │ ├── CheckStatus.php │ └── HostHealth.php ├── Host.php └── Presenters │ ├── CheckPresenter.php │ └── HostPresenter.php ├── Notifications ├── BaseNotification.php ├── EventHandler.php ├── Notifiable.php └── Notifications │ ├── CheckFailed.php │ ├── CheckRestored.php │ ├── CheckSucceeded.php │ └── CheckWarning.php └── ServerMonitorServiceProvider.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: spatie 2 | -------------------------------------------------------------------------------- /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: Check & fix styling 2 | 3 | on: [push] 4 | 5 | jobs: 6 | php-cs-fixer: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | with: 13 | ref: ${{ github.head_ref }} 14 | 15 | - name: Run PHP CS Fixer 16 | uses: docker://oskarstark/php-cs-fixer-ga 17 | with: 18 | args: --config=.php_cs.dist.php --allow-risky=yes 19 | 20 | - name: Commit changes 21 | uses: stefanzweifel/git-auto-commit-action@v4 22 | with: 23 | commit_message: Fix styling 24 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest, windows-latest] 15 | php: [8.4, 8.3, 8.2] 16 | laravel: ['10.*', '11.*', '12.*'] 17 | dependency-version: [prefer-lowest, prefer-stable] 18 | 19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v2 24 | 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php }} 29 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 30 | coverage: none 31 | 32 | - name: Install dependencies 33 | run: | 34 | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update 35 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 36 | 37 | - name: Execute tests 38 | run: vendor/bin/pest 39 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | update: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | with: 15 | ref: main 16 | 17 | - name: Update Changelog 18 | uses: stefanzweifel/changelog-updater-action@v1 19 | with: 20 | latest-version: ${{ github.event.release.name }} 21 | release-notes: ${{ github.event.release.body }} 22 | 23 | - name: Commit updated CHANGELOG 24 | uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | branch: main 27 | commit_message: Update CHANGELOG 28 | file_pattern: CHANGELOG.md 29 | -------------------------------------------------------------------------------- /.php-cs-fixer.cache: -------------------------------------------------------------------------------- 1 | {"php":"8.3.20","version":"3.75.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"braces_position":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline","keep_multiple_spaces_after_comma":true},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","do","else","elseif","final","for","foreach","function","if","interface","namespace","private","protected","public","static","switch","trait","try","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"ordered_imports":{"sort_algorithm":"alpha"},"no_unused_imports":true,"not_operator_with_successor_space":true,"trailing_comma_in_multiline":true,"phpdoc_scalar":true,"unary_operator_spaces":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["break","continue","declare","return","throw","try"]},"phpdoc_single_line_var_spacing":true,"phpdoc_var_without_name":true,"class_attributes_separation":{"elements":{"method":"one"}},"single_trait_insert_per_statement":true},"hashes":{"src\/ServerMonitorServiceProvider.php":"86b579ac3758a049c4bc93cd8c41660c","src\/Helpers\/ConsoleOutput.php":"89e9ab4e83a3143cfce966b3cc905837","src\/HostRepository.php":"62872ab76afd289c30eb259c49bad1e1","src\/Models\/Concerns\/HasCustomProperties.php":"adc30067a40f4eb5e60a62008bbd454a","src\/Models\/Concerns\/HandlesCheckResult.php":"6611f6d3e41666467801f6f9627a076f","src\/Models\/Concerns\/ThrottlesFailingNotifications.php":"ce348e275e1eceb69342c8a60c6f7232","src\/Models\/Concerns\/HasProcess.php":"a39b96e0f4886e1b284331ea620dc3b7","src\/Models\/Check.php":"9f5a5b357adae812d41bb38512ecdd48","src\/Models\/Presenters\/CheckPresenter.php":"84fdb8fa4d1eaf3c97433d84fa74b7d9","src\/Models\/Presenters\/HostPresenter.php":"ecb53a977a8d944b3f67426b23f5d283","src\/Models\/Enums\/HostHealth.php":"7eaf2c468d7be7770e5c7fe6b11a2434","src\/Models\/Enums\/CheckStatus.php":"dafc6b0bf24a0f862932488e01634efb","src\/Models\/Host.php":"050b4f60475076402bab4294bd5c62e0","src\/Commands\/RunChecks.php":"e7a6717c92d1b54cdbc247ac529cb507","src\/Commands\/DeleteHost.php":"11699d2f22db72a596a2bbf114911033","src\/Commands\/DumpChecks.php":"b795a07621346a68cb9e8562a95ab84e","src\/Commands\/ListHosts.php":"d92822d128fbb52ab934a17061f69729","src\/Commands\/BaseCommand.php":"d210078c7671000673cdee671e534cb1","src\/Commands\/AddHost.php":"bc0ba835afbc2879583a7f61732e439c","src\/Commands\/ListChecks.php":"59849ebc3ee0a6cc90dfd53050ca7c34","src\/Commands\/SyncFile.php":"bf9e1d0588b02199d0b024248517b3df","src\/CheckCollection.php":"6658955a69ae8bf6854ece9459221583","src\/CheckDefinitions\/Diskspace.php":"77d07ca2334645bfa94c9930992dff82","src\/CheckDefinitions\/CheckDefinition.php":"5f77b1eeab09cce3a55333fb463eed13","src\/CheckDefinitions\/Memcached.php":"907df8eedd3167537c821151fb6676a0","src\/CheckDefinitions\/Elasticsearch.php":"4f6fc8bc080771f0bdbd268a529b24f5","src\/CheckDefinitions\/MySql.php":"9e3e927ced9f0983a6ff697a370cc139","src\/CheckRepository.php":"6b4b5c61269efde97b62611384680688","src\/Events\/CheckFailed.php":"96785abf933868c80def8e72e2c440a7","src\/Events\/CheckRestored.php":"9112db381feee3e3e6a71fe14cff0f21","src\/Events\/Event.php":"75cd2d161f1b177dcc4c890a23a8e5e5","src\/Events\/CheckSucceeded.php":"d858b6c5a18cf3cd93f45123e404bb4b","src\/Events\/CheckWarning.php":"f7e219f695d3bbc9fb21d2bdf181d14c","src\/Notifications\/BaseNotification.php":"07907ef4c5d9fca40f6beba49c2e08e6","src\/Notifications\/Notifiable.php":"51d610c456dcaf9085a07f0972acdc0e","src\/Notifications\/EventHandler.php":"9d2ed019fb2991b34bc5b7854d0676bf","src\/Notifications\/Notifications\/CheckFailed.php":"099d8c95b3a113af4e8e4444a0f29642","src\/Notifications\/Notifications\/CheckRestored.php":"56ada3c2f736c071e825260009634e62","src\/Notifications\/Notifications\/CheckSucceeded.php":"1c5a24f00c5212b02ff5489b15805db8","src\/Notifications\/Notifications\/CheckWarning.php":"be893feaf39176e52909f64551059ad7","src\/Manipulators\/Manipulator.php":"68af38f317125f23b43e873195b0a515","src\/Manipulators\/Passthrough.php":"bcf3ebb1bbb2afd926e78335d3888c0e","src\/Exceptions\/InvalidConfiguration.php":"c266459fa99e3a391819b054e54d12d3","src\/Exceptions\/InvalidCheckDefinition.php":"e09ef9f263c1200acabf060d002ee7f5","tests\/SshServer.php":"50b01df9e0f71cdd30d2c625b6b5e4fb","tests\/TestCase.php":"57ef1944305ba3d2919091bf34f16a14","tests\/Models\/HostTest.php":"f7c7cde65d6cddb08e661373b575dbf3","tests\/Models\/Concerns\/ThrottlesFailingNotificationsTest.php":"8ed6d8a2ef8ffdb0a1fbe4bcb19d6f05","tests\/Models\/Concerns\/HasCustomPropertiesTest.php":"43a27bfbfdbe2cec1605863b3b8a37f7","tests\/Models\/Concerns\/HasProcessTest.php":"56e5eeec26601ec5408b27f63c215489","tests\/Models\/CheckTest.php":"37844ac6621bdf6c8df7249575332073","tests\/Commands\/ListChecksTest.php":"a7c8d100434a9adf8214881c9b0c4df4","tests\/Commands\/DumpChecksTest.php":"24cbdd4106e00abc8fbfbe8e9af8b5e0","tests\/Commands\/ListHostsTest.php":"897bf2a9f2fec8afe90a5bbb872dd26a","tests\/Commands\/SyncFileTest.php":"1634cff87693090625f422a97a6dcb20","tests\/CheckDefinitions\/MySqlTest.php":"9d42e446798248084e034050b9935e31","tests\/CheckDefinitions\/ElasticsearchTest.php":"1a516e5b8559202b17d44b8a21923078","tests\/CheckDefinitions\/MemcachedTest.php":"ee36088fc3e6680d6d409bb86c2ee109","tests\/CheckDefinitions\/CheckDefinitionTest.php":"cb75ed7e866160cc6ca401ee92ae6700","tests\/CheckDefinitions\/DiskspaceTest.php":"d9f91028b28e0834e3c58455c528dcb4","tests\/Datasets\/events.php":"7104a583ec5e290328cb012ebb4f3d09","tests\/Datasets\/percentage.php":"84fb766ec14e9e4959dc3ff308f2019e","tests\/Events\/CheckSucceededTest.php":"7a11d8305d8ea552b154ca474d06a5c5","tests\/Events\/CheckWarningTest.php":"099b9507e402e3e359379800fa454355","tests\/Events\/CheckRestoredTest.php":"0fea30a2086edc75f184acd4079c308d","tests\/Events\/CheckFailedTest.php":"e16e5022ef2faa43c4b4c3644532ff2f","tests\/IntegrationTest.php":"529a8409eae8c91c26315f0414f7f45c","tests\/Notifications\/EventHandlerTest.php":"fde38929c0511720910f11b22ed86e1b","tests\/Notifications\/NotifiableTest.php":"221d64f8cfe66d4fff293a3256201925","tests\/Pest.php":"4b210445768ea45d6ecc9d8d8f59422a"}} -------------------------------------------------------------------------------- /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR2' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-server-monitor` will be documented in this file 4 | 5 | ## 1.10.1 - 2025-02-21 6 | 7 | ### What's Changed 8 | 9 | * Add type hint to fix deprecation message by @ThobsChoucroute in https://github.com/spatie/laravel-server-monitor/pull/132 10 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-server-monitor/pull/133 11 | 12 | ### New Contributors 13 | 14 | * @ThobsChoucroute made their first contribution in https://github.com/spatie/laravel-server-monitor/pull/132 15 | 16 | **Full Changelog**: https://github.com/spatie/laravel-server-monitor/compare/1.10.0...1.10.1 17 | 18 | ## 1.10.0 - 2024-03-08 19 | 20 | ### What's Changed 21 | 22 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-server-monitor/pull/131 23 | 24 | **Full Changelog**: https://github.com/spatie/laravel-server-monitor/compare/1.9.7...1.10.0 25 | 26 | ## 1.9.7 - 2023-07-19 27 | 28 | ### What's Changed 29 | 30 | - Fix typo by @DieterHolvoet in https://github.com/spatie/laravel-server-monitor/pull/123 31 | 32 | ### New Contributors 33 | 34 | - @DieterHolvoet made their first contribution in https://github.com/spatie/laravel-server-monitor/pull/123 35 | 36 | **Full Changelog**: https://github.com/spatie/laravel-server-monitor/compare/1.9.6...1.9.7 37 | 38 | ## 1.9.6 - 2023-02-23 39 | 40 | ### What's Changed 41 | 42 | - fix for deprecated Model "Dates" Property by @daikazu in https://github.com/spatie/laravel-server-monitor/pull/122 43 | 44 | ### New Contributors 45 | 46 | - @daikazu made their first contribution in https://github.com/spatie/laravel-server-monitor/pull/122 47 | 48 | **Full Changelog**: https://github.com/spatie/laravel-server-monitor/compare/1.9.5...1.9.6 49 | 50 | ## 1.9.5 - 2023-01-25 51 | 52 | ### What's Changed 53 | 54 | - Refactor tests to pest by @AyoobMH in https://github.com/spatie/laravel-server-monitor/pull/118 55 | - Laravel 10.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-server-monitor/pull/121 56 | 57 | ### New Contributors 58 | 59 | - @AyoobMH made their first contribution in https://github.com/spatie/laravel-server-monitor/pull/118 60 | 61 | **Full Changelog**: https://github.com/spatie/laravel-server-monitor/compare/1.9.4...1.9.5 62 | 63 | ## 1.9.4 - 2022-01-19 64 | 65 | - support Laravel 9 66 | 67 | ## 1.9.3 - 2021-02-23 68 | 69 | - add support for PHP 8 70 | 71 | ## 1.9.2 - 2020-09-09 72 | 73 | - add support for Laravel 8 74 | 75 | ## 1.9.1 - 2020-08-20 76 | 77 | - allow Guzzle 7 78 | 79 | ## 1.9.0 - 2020-03-03 80 | 81 | - add support for Laravel 7 82 | 83 | ## 1.8.1 - 2019-09-20 84 | 85 | - `next_run_in_minutes` can be set in config 86 | 87 | ## 1.8.0 - 2019-09-04 88 | 89 | - add support for Laravel 6 90 | 91 | ## 1.7.0 - 2019-02-27 92 | 93 | - drop support for Laravel 5.7 and below 94 | - drop support for PHP 7.1 and below 95 | 96 | ## 1.6.2 - 2019-02-27 97 | 98 | - add support for Laravel 5.8 99 | 100 | ## 1.6.1 - 2019-02-01 101 | 102 | - use Arr:: and Str:: functions 103 | 104 | ## 1.6.0 - 2019-01-31 105 | 106 | - add `dump-checks` command 107 | 108 | ## 1.5.0 - 2019-01-10 109 | 110 | - allow elastic search check to check other ips 111 | 112 | ## 1.4.2 - 2019-01-10 113 | 114 | - fix memcached check 115 | 116 | ## 1.4.1 - 2018-08-27 117 | 118 | - add support for Laravel 5.7 119 | 120 | ## 1.4.0 - 2018-07-02 121 | 122 | - add `ssh_command_prefix` to config file 123 | 124 | ## 1.3.2 - 2018-02-18 125 | 126 | - add support for L5.6 127 | 128 | ## 1.3.1 - 2017-12-15 129 | 130 | - fix missing import in service provider 131 | 132 | ## 1.3.0 - 2017-12-13 133 | 134 | - add ability to specify multiple mail addresses in the notifiable 135 | 136 | ## 1.2.1 - 2017-09-02 137 | 138 | - add support for L5.5 auto discovery 139 | 140 | ## 1.2.0 - 2017-05-01 141 | 142 | - make `Host` model configurable 143 | 144 | ## 1.1.1 - 2017-04-24 145 | 146 | - make diskspace thresholds configurable 147 | 148 | ## 1.1.0 - 2017-04-19 149 | 150 | - the notifiable now has access to the event that triggered the notification 151 | 152 | ## 1.0.9 - 2017-04-10 153 | 154 | - make checks extensible 155 | 156 | ## 1.0.8 - 2017-03-21 157 | 158 | - don't show output of curl in Elasticsearch check 159 | 160 | ## 1.0.7 - 2017-03-07 161 | 162 | - improve the detection of failed processes 163 | 164 | ## 1.0.6 - 2017-03-05 165 | 166 | - fix mail notifications 167 | 168 | ## 1.0.5 - 2017-03-05 169 | 170 | - clean up migrations 171 | 172 | ## 1.0.4 - 2017-03-02 173 | 174 | - fix for bug when using a custom model 175 | 176 | ## 1.0.3 - 2017-03-02 177 | 178 | - further improvements for wrong value in `Next run` in list commands 179 | 180 | ## 1.0.2 - 2017-03-02 181 | 182 | **THIS VERSION IS BROKEN, DO NOT USE** 183 | 184 | - fix for wrong value in `Next run` in list commands 185 | 186 | ## 1.0.1 - 2017-03-02 187 | 188 | **THIS VERSION IS BROKEN, DO NOT USE** 189 | 190 | - fix for wrong value in `Next run` in list commands 191 | 192 | ## 1.0.0 - 2017-03-02 193 | 194 | - initial release 195 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Logo for laravel-server-monitor 6 | 7 | 8 | 9 |

Simple and powerful server monitoring

10 | 11 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-server-monitor.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-server-monitor) 12 | [![run-tests](https://github.com/spatie/laravel-server-monitor/actions/workflows/run-tests.yml/badge.svg)](https://github.com/spatie/laravel-server-monitor/actions/workflows/run-tests.yml) 13 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-server-monitor.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-server-monitor) 14 | 15 |
16 | 17 | We all dream of servers that need no maintenance at all. But unfortunately in reality this is not the case. Disks can get full, processes can crash, the server can run out of memory... 18 | 19 | This package keeps an eye on the health of all your servers. There are a few [checks that come out of the box](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/built-in-checks). [Adding new checks](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/writing-your-own-checks) is a breeze. 20 | 21 | When something goes wrong it can [notify you](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/notifications-and-events) via Slack or mail. 22 | 23 | ## Support us 24 | 25 | [](https://spatie.be/github-ad-click/laravel-server-monitor) 26 | 27 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 28 | 29 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 30 | 31 | ## Documentation 32 | 33 | The full documentation is available on [our documentation site](https://docs.spatie.be/laravel-server-monitor). 34 | 35 | ## Installation 36 | 37 | You can install this package via composer using this command: 38 | 39 | ```bash 40 | composer require spatie/laravel-server-monitor 41 | ``` 42 | 43 | In Laravel 5.5 the service provider will automatically get registered. In older versions of the framework, you must install the service provider: 44 | 45 | ```php 46 | // config/app.php 47 | 'providers' => [ 48 | ... 49 | Spatie\ServerMonitor\ServerMonitorServiceProvider::class, 50 | ]; 51 | ``` 52 | 53 | You can publish the migrations with: 54 | ```bash 55 | php artisan vendor:publish --provider="Spatie\ServerMonitor\ServerMonitorServiceProvider" --tag="migrations" 56 | ``` 57 | 58 | After the migration has been published you can create the `hosts` and `checks `tables by running the migrations: 59 | 60 | ```bash 61 | php artisan migrate 62 | ``` 63 | 64 | You must publish the config-file with: 65 | ```bash 66 | php artisan vendor:publish --provider="Spatie\ServerMonitor\ServerMonitorServiceProvider" --tag="config" 67 | ``` 68 | 69 | This is the contents of the published config file: 70 | 71 | ```php 72 | return [ 73 | 74 | /* 75 | * These are the checks that can be performed on your servers. You can add your own 76 | * checks. The only requirement is that they should extend the 77 | * `Spatie\ServerMonitor\Checks\CheckDefinitions\CheckDefinition` class. 78 | */ 79 | 'checks' => [ 80 | 'diskspace' => Spatie\ServerMonitor\CheckDefinitions\Diskspace::class, 81 | 'elasticsearch' => Spatie\ServerMonitor\CheckDefinitions\Elasticsearch::class, 82 | 'memcached' => Spatie\ServerMonitor\CheckDefinitions\Memcached::class, 83 | 'mysql' => Spatie\ServerMonitor\CheckDefinitions\MySql::class, 84 | ], 85 | 86 | /* 87 | * The performance of the package can be increased by allowing a high number 88 | * of concurrent ssh connections. Set this to a lower value if you're 89 | * getting weird errors running the check. 90 | */ 91 | 'concurrent_ssh_connections' => 5, 92 | 93 | /* 94 | * This string will be appended to the ssh command generated by the package. 95 | */ 96 | 'ssh_command_suffix' => '', 97 | 98 | 'notifications' => [ 99 | 100 | 'notifications' => [ 101 | Spatie\ServerMonitor\Notifications\Notifications\CheckSucceeded::class => [], 102 | Spatie\ServerMonitor\Notifications\Notifications\CheckRestored::class => ['slack'], 103 | Spatie\ServerMonitor\Notifications\Notifications\CheckWarning::class => ['slack'], 104 | Spatie\ServerMonitor\Notifications\Notifications\CheckFailed::class => ['slack'], 105 | ], 106 | 107 | /* 108 | * To avoid burying you in notifications, we'll only send one every given amount 109 | * of minutes when a check keeps emitting warning or keeps failing. 110 | */ 111 | 'throttle_failing_notifications_for_minutes' => 60, 112 | 113 | 'mail' => [ 114 | 'to' => 'your@email.com', 115 | ], 116 | 117 | 'slack' => [ 118 | 'webhook_url' => env('SERVER_MONITOR_SLACK_WEBHOOK_URL'), 119 | ], 120 | 121 | /* 122 | * Here you can specify the notifiable to which the notifications should be sent. The default 123 | * notifiable will use the variables specified in this config file. 124 | */ 125 | 'notifiable' => \Spatie\ServerMonitor\Notifications\Notifiable::class, 126 | 127 | /* 128 | * The date format used in notifications. 129 | */ 130 | 'date_format' => 'd/m/Y', 131 | ], 132 | 133 | /* 134 | * To add or modify behaviour to the `Host` model you can specify your 135 | * own model here. The only requirement is that they should 136 | * extend the `Host` model provided by this package. 137 | */ 138 | 'host_model' => Spatie\ServerMonitor\Models\Host::class, 139 | 140 | /* 141 | * To add or modify behaviour to the `Check` model you can specify your 142 | * own model here. The only requirement is that they should 143 | * extend the `Check` model provided by this package. 144 | */ 145 | 'check_model' => Spatie\ServerMonitor\Models\Check::class, 146 | 147 | /* 148 | * Right before running a check its process will be given to this class. Here you 149 | * can perform some last minute manipulations on it before it will 150 | * actually be run. 151 | * 152 | * This class should implement Spatie\ServerMonitor\Manipulators\Manipulator 153 | */ 154 | 'process_manipulator' => Spatie\ServerMonitor\Manipulators\Passthrough::class, 155 | 156 | /* 157 | * Thresholds for disk space's alert. 158 | */ 159 | 'diskspace_percentage_threshold' => [ 160 | 'warning' => 80, 161 | 'fail' => 90, 162 | ], 163 | ]; 164 | ``` 165 | 166 | ## Need a UI? 167 | 168 | The package doesn't come with any screens out of the box. You may use the [Nova package by @paras-malhotra](https://github.com/insenseanalytics/nova-server-monitor) for monitoring servers on [Laravel Nova](https://nova.laravel.com). 169 | 170 | ## Changelog 171 | 172 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 173 | 174 | ## Testing 175 | 176 | To run the tests you'll have to start the included node based dummy ssh server first in a separate terminal window. 177 | 178 | ```bash 179 | cd tests/server 180 | npm install 181 | ./start_server.sh 182 | ``` 183 | 184 | With the server running, you can start testing. 185 | 186 | ```bash 187 | vendor/bin/phpunit 188 | ``` 189 | 190 | ## Contributing 191 | 192 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 193 | 194 | ## Security 195 | 196 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 197 | 198 | ## Credits 199 | 200 | - [Freek Van der Herten](https://github.com/freekmurze) 201 | - [All Contributors](../../contributors) 202 | 203 | The code to execute commands on a remote server was copied from [Envoy](https://github.com/laravel/envoy). 204 | 205 | ## License 206 | 207 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 208 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-server-monitor", 3 | "description": "Monitor servers", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-server-monitor" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-server-monitor", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "homepage": "https://spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "ext-json": "*", 20 | "php": "^8.2", 21 | "guzzlehttp/guzzle": "^7.0", 22 | "laravel/framework": "^10.0|^11.0|^12.0", 23 | "spatie/laravel-blink": "^1.3", 24 | "spatie/regex": "^3.1", 25 | "symfony/process": "^6.0|^7.0" 26 | }, 27 | "require-dev": { 28 | "mockery/mockery": "^1.4", 29 | "orchestra/testbench": "^8.0|^9.0|^10.0", 30 | "pestphp/pest": "^2.34|^3.7" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Spatie\\ServerMonitor\\": "src" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Spatie\\ServerMonitor\\Test\\": "tests" 40 | } 41 | }, 42 | "scripts": { 43 | "test": "vendor/bin/pest" 44 | }, 45 | "config": { 46 | "sort-packages": true, 47 | "allow-plugins": { 48 | "pestphp/pest-plugin": true 49 | } 50 | }, 51 | "extra": { 52 | "laravel": { 53 | "providers": [ 54 | "Spatie\\ServerMonitor\\ServerMonitorServiceProvider" 55 | ] 56 | } 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true 60 | } 61 | -------------------------------------------------------------------------------- /config/server-monitor.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'diskspace' => Spatie\ServerMonitor\CheckDefinitions\Diskspace::class, 12 | 'elasticsearch' => Spatie\ServerMonitor\CheckDefinitions\Elasticsearch::class, 13 | 'memcached' => Spatie\ServerMonitor\CheckDefinitions\Memcached::class, 14 | 'mysql' => Spatie\ServerMonitor\CheckDefinitions\MySql::class, 15 | ], 16 | 17 | /* 18 | * The default value for how often the checks will run, 19 | * after the last successful one. 20 | */ 21 | 'next_run_in_minutes' => env('SERVER_MONITOR_NEXT_RUN_IN_MINUTES', 10), 22 | 23 | /* 24 | * The performance of the package can be increased by allowing a high number 25 | * of concurrent ssh connections. Set this to a lower value if you're 26 | * getting weird errors running the check. 27 | */ 28 | 'concurrent_ssh_connections' => 5, 29 | 30 | /* 31 | * This string will be prepended to the ssh command generated by the package. 32 | */ 33 | 'ssh_command_prefix' => '', 34 | 35 | /* 36 | * This string will be appended to the ssh command generated by the package. 37 | */ 38 | 'ssh_command_suffix' => '', 39 | 40 | 'notifications' => [ 41 | 42 | 'notifications' => [ 43 | Spatie\ServerMonitor\Notifications\Notifications\CheckSucceeded::class => [], 44 | Spatie\ServerMonitor\Notifications\Notifications\CheckRestored::class => ['slack'], 45 | Spatie\ServerMonitor\Notifications\Notifications\CheckWarning::class => ['slack'], 46 | Spatie\ServerMonitor\Notifications\Notifications\CheckFailed::class => ['slack'], 47 | ], 48 | 49 | /* 50 | * To avoid burying you in notifications, we'll only send one every given amount 51 | * of minutes when a check keeps emitting warning or keeps failing. 52 | */ 53 | 'throttle_failing_notifications_for_minutes' => 60, 54 | 55 | // Separate the email by , to add many recipients 56 | 'mail' => [ 57 | 'to' => 'your@email.com', 58 | ], 59 | 60 | 'slack' => [ 61 | 'webhook_url' => env('SERVER_MONITOR_SLACK_WEBHOOK_URL'), 62 | ], 63 | 64 | /* 65 | * Here you can specify the notifiable to which the notifications should be sent. The default 66 | * notifiable will use the variables specified in this config file. 67 | */ 68 | 'notifiable' => \Spatie\ServerMonitor\Notifications\Notifiable::class, 69 | 70 | /* 71 | * The date format used in notifications. 72 | */ 73 | 'date_format' => 'd/m/Y', 74 | ], 75 | 76 | /* 77 | * To add or modify behaviour to the `Host` model you can specify your 78 | * own model here. The only requirement is that they should 79 | * extend the `Host` model provided by this package. 80 | */ 81 | 'host_model' => Spatie\ServerMonitor\Models\Host::class, 82 | 83 | /* 84 | * To add or modify behaviour to the `Check` model you can specify your 85 | * own model here. The only requirement is that they should 86 | * extend the `Check` model provided by this package. 87 | */ 88 | 'check_model' => Spatie\ServerMonitor\Models\Check::class, 89 | 90 | /* 91 | * Right before running a check its process will be given to this class. Here you 92 | * can perform some last minute manipulations on it before it will 93 | * actually be run. 94 | * 95 | * This class should implement Spatie\ServerMonitor\Manipulators\Manipulator 96 | */ 97 | 'process_manipulator' => Spatie\ServerMonitor\Manipulators\Passthrough::class, 98 | 99 | /* 100 | * Thresholds for disk space's alert. 101 | */ 102 | 'diskspace_percentage_threshold' => [ 103 | 'warning' => 80, 104 | 'fail' => 90, 105 | ], 106 | ]; 107 | -------------------------------------------------------------------------------- /database/migrations/create_checks_table.php.stub: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('host_id')->unsigned(); 18 | $table->foreign('host_id')->references('id')->on('hosts')->onDelete('cascade'); 19 | $table->string('type'); 20 | $table->string('status')->nullable(); 21 | $table->boolean('enabled')->default(true); 22 | $table->text('last_run_message')->nullable(); 23 | $table->json('last_run_output')->nullable(); 24 | $table->timestamp('last_ran_at')->nullable(); 25 | $table->integer('next_run_in_minutes')->nullable(); 26 | $table->timestamp('started_throttling_failing_notifications_at')->nullable(); 27 | $table->json('custom_properties')->nullable(); 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::drop('checks'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /database/migrations/create_hosts_table.php.stub: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name'); 18 | $table->string('ssh_user')->nullable(); 19 | $table->integer('port')->nullable(); 20 | $table->string('ip')->nullable(); 21 | $table->json('custom_properties')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::drop('hosts'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: v1 3 | slogan: Don't let them melt 4 | githubUrl: https://github.com/spatie/laravel-server-monitor 5 | branch: master 6 | --- 7 | -------------------------------------------------------------------------------- /docs/about-us.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About us 3 | weight: 9 4 | --- 5 | 6 | [Spatie](https://spatie.be) is a webdesign agency based in Antwerp, Belgium. 7 | 8 | Open source software is used in all projects we deliver. Laravel, Nginx, Ubuntu are just a few of the free pieces of software we use every single day. For this, we are very grateful. 9 | When we feel we have solved a problem in a way that can help other developers, we release our code as open source software [on GitHub](https://spatie.be/opensource). 10 | 11 | This server monitor package was made by [Freek Van der Herten](https://twitter.com/freekmurze) and code reviewed by [Sebastian De Deyne](https://github.com/sebastiandedeyne). A big thank you to [Gilbert West](https://github.com/blueclock) for proofreading and fixing errors in the docs. On GitHub you can find [a list of other contributors](https://github.com/spatie/laravel-server-monitor/graphs/contributors) who devoted time and effort to make this package better. 12 | -------------------------------------------------------------------------------- /docs/advanced-usage/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced usage 3 | weight: 2 4 | --- 5 | -------------------------------------------------------------------------------- /docs/advanced-usage/customizing-notifications.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Customizing notifications 3 | weight: 3 4 | --- 5 | 6 | This package leverages [Laravel's native notification capabilites](https://laravel.com/docs/5.4/notifications) to send out [notifications](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/notifications-and-events). 7 | 8 | ```php 9 | 'notifications' => [ 10 | Spatie\ServerMonitor\Notifications\Notifications\CheckSucceeded::class => [], 11 | Spatie\ServerMonitor\Notifications\Notifications\CheckRestored::class => ['slack'], 12 | Spatie\ServerMonitor\Notifications\Notifications\CheckWarning::class => ['slack'], 13 | Spatie\ServerMonitor\Notifications\Notifications\CheckFailed::class => ['slack'], 14 | ], 15 | ``` 16 | 17 | Notice that the config keys are fully qualified class names of the `Notification` classes. All notifications have support for `slack` and `mail` out of the box. If you want to add support for more channels or just want to change the text of the notifications you can specify your own notification classes in the config file. When creating custom notifications, it's best to extend the default ones shipped with this package. 18 | -------------------------------------------------------------------------------- /docs/advanced-usage/manipulating-processes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Manipulating processes 3 | weight: 4 4 | --- 5 | 6 | Under the hood `Symfony\Component\Process` is used to connect to your server and performing the command from your check. You can manipulate this `Process` right before it is executed. 7 | 8 | To do this you must create a class that implements `Spatie\ServerMonitor\Manipulators\Manipulator`. That interface has only a single method to implement: 9 | 10 | ```php 11 | public function manipulateProcess(Process $process, Check $check): Process; 12 | ``` 13 | 14 | Let's take a look at an example implementation. In the code below we're going to change the command and up the timeout if the check is being performed on a certain host. 15 | 16 | ```php 17 | namespace App\ServerMonitor\Manipulators; 18 | 19 | use Spatie\ServerMonitor\Models\Check; 20 | use Symfony\Component\Process\Process; 21 | 22 | class MyManipulator implements Manipulator 23 | { 24 | public function manipulateProcess(Process $process, Check $check): Process 25 | { 26 | if ($check->host->name = 'my-host') { 27 | $manipulatedCommand = "{$process->getCommandLine()} appending extra options"; 28 | 29 | $process->setCommandLine($manipulatedCommand); 30 | 31 | $process->setTimeout(60); 32 | } 33 | 34 | return $process; 35 | } 36 | } 37 | 38 | ``` 39 | 40 | After creating the class you must specify it's fully qualified name in the `process_manipulator` key of the `server-monitor` config file. 41 | -------------------------------------------------------------------------------- /docs/advanced-usage/manually-configure-hosts-and-checks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Manually modifying hosts and checks 3 | weight: 1 4 | --- 5 | 6 | All configured checks are stored in the `checks` table in the database. Every check is related to one host from the `hosts` table. The various `server-monitor` commands manipulate the data these two tables: 7 | 8 | - `server-monitor:add-host` adds a host in the `hosts` table and creates checks in the `check` table related to that host. 9 | - `server-monitor:delete-host` deletes a host and all related checks 10 | - `server-monitor:list-hosts` lists all hosts 11 | - `server-monitor:list-checks` lists detailed information about all checks 12 | 13 | You can also manually manipulate the rows of both tables. These fields can be manipulated in the `hosts` table: 14 | 15 | - `name`: the name of the host that will be checked. 16 | - `ssh_user`: the name of the ssh user the package should use when connecting to the remote server. 17 | - `port`: the port that should be used when connecting to the server. If this is empty port 22 will be used. 18 | - `ip`: if this field contains an ip-address we'll use that instead of the `name` when connecting to a server 19 | - `custom_properties`: see the section on [using custom properties](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/writing-your-own-checks#using-custom-properties) 20 | 21 | These are the fields you can manipulate in the `checks` table: 22 | 23 | - `host_id`: the `id` of the host in the `hosts` table on which this check will be performed. 24 | - `type`: this value determines which check should be performed. The value should correspond to one of the keys in `checks` keys in the config file eg `diskspace`, `mysql`, ... 25 | - `enabled`: if this contains `0` the check won't be executed. 26 | - `custom_properties`: see the section on [using custom properties](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/writing-your-own-checks#using-custom-properties) 27 | 28 | All other fields in the `checks` and `hosts` tables are managed by the package and should not be manually modified. 29 | 30 | -------------------------------------------------------------------------------- /docs/advanced-usage/using-your-own-model.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using your own model 3 | weight: 2 4 | --- 5 | 6 | By default this package uses the `Spatie\ServerMonitor\Models\Check` model. If you want to add some extra functionality you can specify your own model in the `check_model` key of the config file. The only requirement for your custom model is that is should extend `Spatie\ServerMonitor\Models\Check`. 7 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | weight: 8 4 | --- 5 | 6 | All notable changes to `laravel-server-monitor` are documented in [the changelog on GitHub](https://github.com/spatie/laravel-server-monitor/blob/master/CHANGELOG.md). 7 | -------------------------------------------------------------------------------- /docs/high-level-overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: High level overview 3 | weight: 4 4 | --- 5 | 6 | This package can perform health checks on all your servers. It does this by ssh'ing into them and performing certain commands. It'll interpret the output returned by the command to determine if the check failed or not. 7 | 8 | Let's illustrate this with the `memcached` check provided out of the box. This verifies if [Memcached](https://memcached.org/) is running. The check runs `service memcached status` on your server and if it outputs a string that contains `memcached is running` the check will succeed. If not, the check will fail. 9 | 10 | When a check fails, and on other events, the package can send you a notification. Notifications looks like this in Slack. 11 | 12 | 13 | 14 | You can specify which channels will send notifications [in the config file](https://docs.spatie.be/laravel-server-monitor/v1/installation-and-setup#basic-installation). By default the package has support for [Slack](https://slack.com/) and mail notifications. Because the package leverages Laravel's native notifications you can use any of the [community supported drivers](https://github.com/laravel-notification-channels) or [write your own](https://laravel.com/docs/5.4/notifications#custom-channels). 15 | 16 | Hosts and checks can be added via the [`add-host` artisan command](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/managing-hosts#adding-hosts) or by manually [adding them](https://docs.spatie.be/laravel-server-monitor/v1/advanced-usage/manually-configure-hosts-and-checks) in the `hosts` and `checks` table. 17 | 18 | This package comes with a few [built in checks](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/built-in-checks). But it's laughably easy to add your [own checks](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/writing-your-own-checks). 19 | -------------------------------------------------------------------------------- /docs/images/add-host.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/add-host.jpg -------------------------------------------------------------------------------- /docs/images/authenticity.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/authenticity.jpg -------------------------------------------------------------------------------- /docs/images/check-failed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/check-failed.jpg -------------------------------------------------------------------------------- /docs/images/check-restored.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/check-restored.jpg -------------------------------------------------------------------------------- /docs/images/check-succeeded.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/check-succeeded.jpg -------------------------------------------------------------------------------- /docs/images/check-warning.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/check-warning.jpg -------------------------------------------------------------------------------- /docs/images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/header.jpg -------------------------------------------------------------------------------- /docs/images/list-checks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/list-checks.jpg -------------------------------------------------------------------------------- /docs/images/list-hosts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/list-hosts.jpg -------------------------------------------------------------------------------- /docs/images/nginx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-server-monitor/76165bd33eeded4c9ff1fbf33f9dabd97d899f2d/docs/images/nginx.jpg -------------------------------------------------------------------------------- /docs/installation-and-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation and setup 3 | weight: 5 4 | --- 5 | 6 | ## Basic installation 7 | 8 | This package should be installed in an existing Laravel application. If you're not familiar with Laravel head over to [the official documentation](https://laravel.com/docs) to learn how to set up and use this amazing framework. 9 | 10 | From the directory of an existing Laravel application you can install the package via composer: 11 | 12 | ``` bash 13 | composer require spatie/laravel-server-monitor:^1.0 14 | ``` 15 | 16 | You'll need to register the service provider: 17 | 18 | ```php 19 | // config/app.php 20 | 21 | 'providers' => [ 22 | // ... 23 | Spatie\ServerMonitor\ServerMonitorServiceProvider::class, 24 | ]; 25 | ``` 26 | 27 | You can publish the migrations with: 28 | ```bash 29 | php artisan vendor:publish --provider="Spatie\ServerMonitor\ServerMonitorServiceProvider" --tag="migrations" 30 | ``` 31 | 32 | After the migration has been published you can create the `hosts` and `checks `tables by running the migrations: 33 | 34 | ```bash 35 | php artisan migrate 36 | ``` 37 | 38 | To publish the config file to `config/server-monitor.php` run: 39 | 40 | ``` bash 41 | php artisan vendor:publish --provider="Spatie\ServerMonitor\ServerMonitorServiceProvider" --tag="config" 42 | ``` 43 | 44 | By default, the configuration looks like this: 45 | 46 | ```php 47 | return [ 48 | 49 | /* 50 | * These are the checks that can be performed on your servers. You can add your own 51 | * checks. The only requirement is that they should extend the 52 | * `Spatie\ServerMonitor\Checks\CheckDefinitions\CheckDefinition` class. 53 | */ 54 | 'checks' => [ 55 | 'diskspace' => Spatie\ServerMonitor\CheckDefinitions\Diskspace::class, 56 | 'elasticsearch' => Spatie\ServerMonitor\CheckDefinitions\Elasticsearch::class, 57 | 'memcached' => Spatie\ServerMonitor\CheckDefinitions\Memcached::class, 58 | 'mysql' => Spatie\ServerMonitor\CheckDefinitions\MySql::class, 59 | ], 60 | 61 | /* 62 | * The default value for how often the checks will run, 63 | * after the last successful one. 64 | */ 65 | 'next_run_in_minutes' => env('SERVER_MONITOR_NEXT_RUN_IN_MINUTES', 10), 66 | 67 | /* 68 | * The performance of the package can be increased by allowing a high number 69 | * of concurrent ssh connections. Set this to a lower value if you're 70 | * getting weird errors running the check. 71 | */ 72 | 'concurrent_ssh_connections' => 5, 73 | 74 | /* 75 | * This string will be appended to the ssh command generated by the package. 76 | */ 77 | 'ssh_command_suffix' => '', 78 | 79 | 'notifications' => [ 80 | 81 | 'notifications' => [ 82 | Spatie\ServerMonitor\Notifications\Notifications\CheckSucceeded::class => [], 83 | Spatie\ServerMonitor\Notifications\Notifications\CheckRestored::class => ['slack'], 84 | Spatie\ServerMonitor\Notifications\Notifications\CheckWarning::class => ['slack'], 85 | Spatie\ServerMonitor\Notifications\Notifications\CheckFailed::class => ['slack'], 86 | ], 87 | 88 | /* 89 | * To avoid burying you in notifications, we'll only send one every given amount 90 | * of minutes when a check keeps emitting warning or keeps failing. 91 | */ 92 | 'throttle_failing_notifications_for_minutes' => 60, 93 | 94 | 'mail' => [ 95 | 'to' => 'your@email.com', 96 | ], 97 | 98 | 'slack' => [ 99 | 'webhook_url' => env('SERVER_MONITOR_SLACK_WEBHOOK_URL'), 100 | ], 101 | 102 | /* 103 | * Here you can specify the notifiable to which the notifications should be sent. The default 104 | * notifiable will use the variables specified in this config file. 105 | */ 106 | 'notifiable' => \Spatie\ServerMonitor\Notifications\Notifiable::class, 107 | 108 | /* 109 | * The date format used in notifications. 110 | */ 111 | 'date_format' => 'd/m/Y', 112 | ], 113 | 114 | /* 115 | * To add or modify behaviour to the `Host` model you can specify your 116 | * own model here. The only requirement is that they should 117 | * extend the `Host` model provided by this package. 118 | */ 119 | 'host_model' => Spatie\ServerMonitor\Models\Host::class, 120 | 121 | /* 122 | * To add or modify behaviour to the `Check` model you can specify your 123 | * own model here. The only requirement is that they should 124 | * extend the `Check` model provided by this package. 125 | */ 126 | 'check_model' => Spatie\ServerMonitor\Models\Check::class, 127 | 128 | /* 129 | * Right before running a check it's process will be given to this class. Here you 130 | * can perform some last minute manipulations on it before it will 131 | * actually be run. 132 | * 133 | * This class should implement Spatie\ServerMonitor\Manipulators\Manipulator 134 | */ 135 | 'process_manipulator' => Spatie\ServerMonitor\Manipulators\Passthrough::class, 136 | ]; 137 | ``` 138 | 139 | ## Scheduling 140 | 141 | After performing the basic installation schedule the `server-monitor:run-checks` command to run every minute. 142 | 143 | ```php 144 | // app/Console/Kernel.php 145 | 146 | protected function schedule(Schedule $schedule) 147 | { 148 | $schedule->command('server-monitor:run-checks')->withoutOverlapping()->everyMinute(); 149 | } 150 | ``` 151 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | weight: 1 4 | --- 5 | 6 | We all dream of servers that need no maintenance at all. But unfortunately in reality this is not the case. Disks can get full, processes can crash, servers run out of memory... 7 | 8 | This package keeps an eye on the health of all your servers. There are a few [checks that come out of the box](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/built-in-checks). [Adding new checks](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/writing-your-own-checks) is a breeze. 9 | 10 | When something goes wrong it can [notify you](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/notifications-and-events) via Slack or mail. Here's what a Slack notification looks like: 11 | 12 | 13 | 14 | Behind the scenes [Laravel's native notification system](https://laravel.com/docs/5.4/notifications) is leveraged so you can use one of the [many notification drivers](http://laravel-notification-channels.com/). 15 | 16 | ## We have badges! 17 | 18 |
19 | Latest Version 20 | Software License 21 | Build Status 22 | Quality Score 23 | StyleCI 24 | Total Downloads 25 |
26 | -------------------------------------------------------------------------------- /docs/monitoring-basics/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Monitoring basics 3 | weight: 1 4 | --- 5 | -------------------------------------------------------------------------------- /docs/monitoring-basics/built-in-checks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Built in checks 3 | weight: 2 4 | --- 5 | 6 | This package comes with a few built in checks and notifications to get you started. Need more? [Write your own](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/writing-your-own-checks), it's easy! 7 | 8 | ### Diskspace 9 | 10 | This check verifies the percentage of diskspace usage on the primary disk. 11 | 12 | It executes this command on the server: `df -P .`. 13 | 14 | If the reported diskspace is below 80% the check will succeed. If the diskspace usage is 80% or above a warning will be sent. If the reported diskspace is above 90% the check will be marked as failed. 15 | 16 | ### Elasticsearch 17 | 18 | This check verifies if [Elasticsearch](https://www.elastic.co/) is running. 19 | 20 | It executes this command on the server: `curl http://localhost:9200`. 21 | 22 | If the output contains `lucene_version` the check will succeed, otherwise it will fail. 23 | 24 | ### Memcached 25 | 26 | This check verifies if [Memcached](https://memcached.org/) is running. 27 | 28 | It executes this command on the server: `service memcached status`. 29 | 30 | If the output contains `memcached` the check will succeed, otherwise it will fail. 31 | 32 | ### MySQL 33 | 34 | This check verifies if [MySQL](https://www.mysql.com/) is running. 35 | 36 | It executes this command on the server: `ps -e | grep mysqld$`. 37 | 38 | If the output contains `mysql` the check will succeed, otherwise it will fail. 39 | -------------------------------------------------------------------------------- /docs/monitoring-basics/listing-hosts-and-checks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Listing hosts and checks 3 | weight: 3 4 | --- 5 | 6 | You can list all configured hosts with: 7 | 8 | ```bash 9 | php artisan server-monitor:list-hosts 10 | ``` 11 | 12 | 13 | 14 | You can list all configured checks with: 15 | 16 | ```bash 17 | php artisan server-monitor:list-checks 18 | ``` 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/monitoring-basics/managing-hosts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Managing hosts 3 | weight: 1 4 | --- 5 | 6 | 7 | 8 | ## Adding hosts 9 | 10 | You can add hosts by running: 11 | 12 | ```bash 13 | php artisan server-monitor:add-host 14 | ``` 15 | 16 | You'll be prompted for the name of your host, the ssh user and the port that should be used to connect to the server and which checks it should run. 17 | 18 | On most systems the authenticity of the host will be verified when connecting to it for the first time. To avoid problems while running the check we recommend manually opening up an ssh connection to the server you want to monitor to get past that check. 19 | 20 | Although we don't recommend this, you could opt to [disable the host authenticity check](http://linuxcommando.blogspot.be/2008/10/how-to-disable-ssh-host-key-checking.html) altogether. Be aware that this will leave yourself open to man in the middle attacks. If you want to go ahead with this option add `-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -q` to the `ssh_command_suffix` key in the `server-monitor` config file. 21 | 22 | You can also prefix the SSH command. Just add your desired prefix to the `ssh_command_prefix` key in the config file. 23 | 24 | ## Deleting hosts 25 | 26 | Deleting hosts is a simple as running 27 | 28 | ```bash 29 | php artisan server-monitor:delete-host 30 | ``` 31 | 32 | where `` is the name of the host you wish to delete. 33 | 34 | ## Syncing from a file 35 | 36 | If you have a large number of hosts that you wish to monitor using the `server-monitor:add-host` becomes tedious fast. Luckily there's also a command to bulk import hosts and check from a json file: 37 | 38 | ``` 39 | php artisan server-monitor:sync-file 40 | ``` 41 | 42 | Here's an example of the structure that json file should have: 43 | 44 | ```json 45 | [ 46 | { 47 | "name": "my-site.com", 48 | "ssh_user": "forge", 49 | "ip": "1.2.3.4", 50 | "checks": [ 51 | "diskspace", "mysql" 52 | ] 53 | }, 54 | { 55 | "name": "another-site.be", 56 | "ssh_user": "forge", 57 | "checks": [ 58 | "diskspace" 59 | ] 60 | } 61 | ] 62 | ``` 63 | 64 | ## Manually modifying hosts and checks 65 | 66 | Instead of using artisan commands you may opt to [manually configure](https://docs.spatie.be/laravel-server-monitor/v1/advanced-usage/manually-configure-hosts-and-checks) the hosts and checks in the database 67 | -------------------------------------------------------------------------------- /docs/monitoring-basics/notifications-and-events.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Notifications and events 3 | weight: 5 4 | --- 5 | 6 | The package notifies you if certain events take place when running checks on your server. You can specify which channels the notifications for certain events should be sent in the config file. If you don't want any notifications for a particular event, just pass an empty array. Out of the box `slack` and `mail` are supported. If you want to use another channel or modify the notifications, read the section on [customizing notifications](https://docs.spatie.be/laravel-server-monitor/v1/advanced-usage/customizing-notifications). 7 | 8 | ## Throttling notifications 9 | 10 | To avoid to burying you in notifications when a check fails or emits a warning, we throttle notifications for such events. 11 | 12 | By default we only send you one notification an hour per warning or failure per check. When a check succeeds again we'll notify you as soon as possible. 13 | 14 | In the [config file](https://docs.spatie.be/laravel-server-monitor/v1/installation-and-setup) your can customize the throttling behaviour by passing a differente value to `throttle_failing_notifications_for_minutes`. 15 | 16 | ## Available notifications 17 | 18 | ### CheckFailed 19 | 20 | `Spatie\ServerMonitor\Notifications\Notifications\CheckFailed` 21 | 22 | This notification is sent when calling `$this-check->fail()` in a check. This also causes the `Spatie\ServerMonitor\Events\CheckFailed`-event to fire. 23 | 24 | This is how the notification looks in Slack. 25 | 26 | 27 | 28 | ### CheckWarning 29 | 30 | `Spatie\ServerMonitor\Notifications\Notifications\CheckWarning` 31 | 32 | This notification is sent when calling `$this-check->warn()` in a check. This also causes the `Spatie\ServerMonitor\Events\CheckWarning`-event to fire. 33 | 34 | The notification looks like this in Slack. 35 | 36 | 37 | 38 | ### CheckRestored 39 | 40 | `Spatie\ServerMonitor\Notifications\Notifications\CheckRestored` 41 | 42 | This notification is sent when a check succeeds after it had been failing. The 43 | `Spatie\ServerMonitor\Events\CheckRestored` event will be fired as well. 44 | 45 | The notification looks like this in Slack. 46 | 47 | 48 | 49 | ### CheckSucceeded 50 | 51 | `Spatie\ServerMonitor\Notifications\Notifications\CheckSucceeded` 52 | 53 | This notification is sent when calling `$this-check->succeed()` in a check. This also fires the `Spatie\ServerMonitor\Events\CheckSucceeded`-event. 54 | 55 | You probably don't want to be notified of this event as it is fired many many times. 56 | -------------------------------------------------------------------------------- /docs/monitoring-basics/writing-your-own-checks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Writing your own checks 3 | weight: 4 4 | --- 5 | 6 | Writing your own checks is very easy. Let's create a check that'll verify if `nginx` is running. 7 | 8 | Let's take a look at how to manually verify if Nginx is running. The easiest way is to run `systemctl is-active nginx`. This command outputs `active` if Nginx is running. 9 | 10 | 11 | 12 | Let's create an automatic check using that command. 13 | 14 | The first thing you must to do is create a class that extends from `Spatie\ServerMonitor\CheckDefinitions\CheckDefinition`. Here's an example implementation. 15 | 16 | ```php 17 | namespace App\MyChecks; 18 | 19 | use Spatie\ServerMonitor\CheckDefinitions\CheckDefinition; 20 | use Symfony\Component\Process\Process; 21 | 22 | class Nginx extends CheckDefinition 23 | { 24 | public $command = 'systemctl is-active nginx'; 25 | 26 | public function resolve(Process $process) 27 | { 28 | if (trim($process->getOutput()) === 'active') { 29 | $this->check->succeed('is running'); 30 | 31 | return; 32 | } 33 | 34 | $this->check->fail('is not running'); 35 | } 36 | } 37 | ``` 38 | 39 | Let's go over this code in detail. The command to be executed on the server is specified in the `$command` property of the class. 40 | 41 | The `resolve` function that accepts an instance of `Symfony\Component\Process\Process`. The output of that `process` can be inspected using `$process->getOutput()`. If the output contains `active` we'll call `$this->check->succeeded` which will mark the check successful. If it does not contain that string `$this->check->fail` will be called and the check marked as failed. By default the package [sends you a notification](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/notifications-and-events) whenever a check fails. The string that is passed to `$this->check->failed` will be displayed in the notification. 42 | 43 | After creating this class you must register your class in the config file. 44 | 45 | ```php 46 | // config/server-monitor.php 47 | 'checks' => [ 48 | ... 49 | 'nginx' => App\MyChecks\Nginx::class, 50 | ], 51 | ``` 52 | 53 | ### Determining when a check will run the next 54 | 55 | If you scheduled `php artisan server-monitor:run-checks`, [like we recommended](https://docs.spatie.be/laravel-server-monitor/v1/installation-and-setup#scheduling), to run every minute a successful check will run again 10 minutes later. If it fails it'll be run again the next minute. 56 | 57 | This behaviour is defined on the `Spatie\ServerMonitor\CheckDefinitions\CheckDefinition` class where all `CheckDefinitions` are extending from. 58 | 59 | ```php 60 | // in class Spatie\ServerMonitor\CheckDefinitions\CheckDefinition 61 | 62 | public function performNextRunInMinutes(): int 63 | { 64 | if ($this->check->hasStatus(CheckStatus::SUCCESS)) { 65 | return 10; 66 | } 67 | 68 | return 0; 69 | ``` 70 | 71 | You may override that function in your own check. 72 | 73 | ### Setting the timeout of a command 74 | 75 | When executing a command on the server a timeout of 10 seconds will be used. If a command takes longer than that the check will be marked as failed. 76 | 77 | This behaviour is defined in the `Spatie\ServerMonitor\CheckDefinitions\CheckDefinition` class from which all `CheckDefinitions` are extended. 78 | 79 | ```php 80 | public function timeoutInSeconds(): int 81 | { 82 | return 10; 83 | } 84 | ``` 85 | 86 | Need a different timeout? Just override the `timeoutInSeconds` function in your own check. 87 | 88 | ### Handling failed commands 89 | 90 | Whenever your command fails, e.g. because a connection to the host can't be made or your command is invalid, `handleFailedProcess` will be called. 91 | 92 | This is the default implementation on `Spatie\ServerMonitor\CheckDefinitions\CheckDefinition`: 93 | 94 | ```php 95 | public function handleFailedProcess(Process $process) 96 | { 97 | $this->check->failed("failed to run: {$process->getErrorOutput()}"); 98 | } 99 | ``` 100 | 101 | Again, if you which to customize this behaviour, you can override that function in your own check. 102 | 103 | ### Using custom properties 104 | 105 | Both the check and the host can retrieve and store custom properties. These properties are stored as json in the `custom_properties` field in the `checks` and `hosts` tables. 106 | 107 | Here's how to work with custom properties: 108 | 109 | ```php 110 | // a $model can be instance of `host` or `check` 111 | $model->setCustomProperty('key', 'value'); 112 | $model->getCustomProperty('key'); // returns 'value' 113 | 114 | $model->forgetCustomProperty('key'); 115 | $model->getCustomProperty('key'); // returns null 116 | ``` 117 | 118 | You can retrieve custom properties from your checks like this: 119 | 120 | ```php 121 | public function handleFailedProcess(Process $process) 122 | { 123 | ... 124 | 125 | $customValueStoredOnCheck = $this->check->getCustomProperty('key'); 126 | 127 | $customValueStoredOnHost = $this->check->host->getCustomProperty('key'); 128 | 129 | ... 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /docs/postcardware.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Postcardware 3 | weight: 2 4 | --- 5 | 6 | You're free to use this package (it's [MIT-licensed](https://github.com/spatie/laravel-server-monitor/blob/master/LICENSE.md)), but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 7 | 8 | Our address is: Spatie, Kruikstraat 22 box 12, 2018 Antwerp, Belgium. 9 | 10 | All received postcards are published [on our website](https://spatie.be/en/opensource/postcards). 11 | -------------------------------------------------------------------------------- /docs/questions-and-issues.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Questions & issues 3 | weight: 7 4 | --- 5 | 6 | Find yourself stuck using the package? Found a bug? Do you have general questions or suggestions for improving this package? Feel free to [create an issue on GitHub](https://github.com/spatie/laravel-server-monitor/issues), we'll try to address it as soon as possible. 7 | 8 | If you've found a bug regarding security please mail [freek@spatie.be](mailto:freek@spatie.be) instead of using the issue tracker. 9 | -------------------------------------------------------------------------------- /docs/requirements.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Requirements 3 | weight: 3 4 | --- 5 | This package requires **PHP 7.1 or higher** and **Laravel 5.4 or higher**. 6 | 7 | The [built in checks](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/built-in-checks) were tested on [Forge](https://forge.laravel.com) provisioned Ubuntu 16.04 servers. If those check do not work for you, [write your own](https://docs.spatie.be/laravel-server-monitor/v1/monitoring-basics/writing-your-own-checks). It's easy! 8 | -------------------------------------------------------------------------------- /docs/using-the-stand-alone-version.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using the stand alone version 3 | weight: 6 4 | --- 5 | 6 | If you're not familiar with Laravel, installing a package can be a bit daunting. That's why we also created a stand alone version called [server-monitor-app](https://github.com/spatie/server-monitor-app). Under the hood it's simply a vanilla Laravel 5.4 application with the laravel-server-monitor package pre-installed into it. 7 | 8 | Using this app you can set up server monitoring in literally one minute. Here's a video that demonstrates the installation and using a check. 9 | 10 | -------------------------------------------------------------------------------- /src/CheckCollection.php: -------------------------------------------------------------------------------- 1 | pendingChecks = $checks; 21 | 22 | $this->runningChecks = collect(); 23 | } 24 | 25 | public function runAll() 26 | { 27 | while ($this->pendingChecks->isNotEmpty() || $this->runningChecks->isNotEmpty()) { 28 | if ($this->runningChecks->count() < config('server-monitor.concurrent_ssh_connections')) { 29 | $this->startNextCheck(); 30 | } 31 | 32 | $this->handleFinishedChecks(); 33 | } 34 | } 35 | 36 | protected function startNextCheck() 37 | { 38 | if ($this->pendingChecks->isEmpty()) { 39 | return; 40 | } 41 | 42 | $check = $this->pendingChecks->shift(); 43 | 44 | ConsoleOutput::comment($check->host->name.": performing check `{$check->type}`..."); 45 | 46 | $check->getProcess()->start(); 47 | 48 | $this->runningChecks->push($check); 49 | } 50 | 51 | protected function handleFinishedChecks() 52 | { 53 | [$this->runningChecks, $finishedChecks] = $this->runningChecks->partition(function (Check $check) { 54 | return $check->getProcess()->isRunning(); 55 | }); 56 | 57 | $finishedChecks->each->handleFinishedProcess(); 58 | } 59 | 60 | public function count(): int 61 | { 62 | return count($this->pendingChecks); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/CheckDefinitions/CheckDefinition.php: -------------------------------------------------------------------------------- 1 | check = $check; 23 | 24 | return $this; 25 | } 26 | 27 | public function command(): string 28 | { 29 | return $this->command; 30 | } 31 | 32 | public function determineResult(Process $process) 33 | { 34 | $this->check->storeProcessOutput($process); 35 | 36 | try { 37 | if (! empty($process->getErrorOutput())) { 38 | $this->resolveFailed($process); 39 | 40 | return; 41 | } 42 | 43 | $this->resolve($process); 44 | } catch (Exception $exception) { 45 | $this->check->fail('Exception occurred: '.$exception->getMessage()); 46 | } 47 | } 48 | 49 | abstract public function resolve(Process $process); 50 | 51 | public function resolveFailed(Process $process) 52 | { 53 | $this->check->fail("failed to run: {$process->getErrorOutput()}"); 54 | } 55 | 56 | /** 57 | * When a check is emitting a warning or is failing, a notification will only 58 | * be sent once in given amount of minutes. 59 | * 60 | * @return int 61 | */ 62 | public function throttleFailingNotificationsForMinutes(): int 63 | { 64 | return config('server-monitor.notifications.throttle_failing_notifications_for_minutes'); 65 | } 66 | 67 | public function performNextRunInMinutes(): int 68 | { 69 | if ($this->check->hasStatus(CheckStatus::SUCCESS)) { 70 | return (int) config('server-monitor.next_run_in_minutes'); 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | /** 77 | * The amount of seconds that check may run. 78 | * 79 | * @return int 80 | */ 81 | public function timeoutInSeconds(): int 82 | { 83 | return 10; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/CheckDefinitions/Diskspace.php: -------------------------------------------------------------------------------- 1 | getDiskUsagePercentage($process->getOutput()); 15 | 16 | $message = "usage at {$percentage}%"; 17 | 18 | $thresholds = config('server-monitor.diskspace_percentage_threshold', [ 19 | 'warning' => 80, 20 | 'fail' => 90, 21 | ]); 22 | 23 | if ($percentage >= $thresholds['fail']) { 24 | $this->check->fail($message); 25 | 26 | return; 27 | } 28 | 29 | if ($percentage >= $thresholds['warning']) { 30 | $this->check->warn($message); 31 | 32 | return; 33 | } 34 | 35 | $this->check->succeed($message); 36 | } 37 | 38 | protected function getDiskUsagePercentage(string $commandOutput): int 39 | { 40 | return (int) Regex::match('/(\d?\d)%/', $commandOutput)->group(1); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/CheckDefinitions/Elasticsearch.php: -------------------------------------------------------------------------------- 1 | command; 15 | 16 | $customIp = $this->check->getCustomProperty('ip'); 17 | 18 | if (! empty($customIp)) { 19 | $command = str_replace('localhost', $customIp, $command); 20 | } 21 | 22 | return $command; 23 | } 24 | 25 | public function resolve(Process $process) 26 | { 27 | $checkSucceeded = Str::contains($process->getOutput(), 'lucene_version'); 28 | 29 | if ($checkSucceeded) { 30 | $this->check->succeed('is running'); 31 | 32 | return; 33 | } 34 | 35 | $this->check->fail('is not running'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CheckDefinitions/Memcached.php: -------------------------------------------------------------------------------- 1 | getOutput(), ['memcached is running', 'active (running)'])) { 15 | $this->check->succeed('is running'); 16 | 17 | return; 18 | } 19 | 20 | $this->check->fail('is not running'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/CheckDefinitions/MySql.php: -------------------------------------------------------------------------------- 1 | getOutput(), 'mysql')) { 15 | $this->check->succeed('is running'); 16 | 17 | return; 18 | } 19 | 20 | $this->check->fail('is not running'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/CheckRepository.php: -------------------------------------------------------------------------------- 1 | get()->filter->shouldRun(); 14 | 15 | return new CheckCollection($checks); 16 | } 17 | 18 | protected static function query(): Builder 19 | { 20 | $modelClass = static::determineCheckModel(); 21 | 22 | return $modelClass::enabled(); 23 | } 24 | 25 | public static function determineCheckModel(): string 26 | { 27 | $monitorModel = config('server-monitor.check_model') ?? Check::class; 28 | 29 | if (! is_a($monitorModel, Check::class, true)) { 30 | throw InvalidConfiguration::checkModelIsNotValid($monitorModel); 31 | } 32 | 33 | return $monitorModel; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Commands/AddHost.php: -------------------------------------------------------------------------------- 1 | '; 15 | 16 | public function handle() 17 | { 18 | $this->info("Let's add a host!"); 19 | 20 | $hostName = $this->ask('What is the name of the host'); 21 | 22 | $sshUser = $this->confirm('Should a custom ssh user be used?') 23 | ? $this->ask('Which user?') 24 | : null; 25 | 26 | $port = $this->confirm('Should a custom port be used?') 27 | ? $this->ask('Which port?') 28 | : null; 29 | 30 | $ip = $this->confirm('Should a specific ip address be used?') 31 | ? $this->ask('Which ip address?') 32 | : null; 33 | 34 | $checkNames = array_merge([static::$allChecksLabel], $this->getAllCheckNames()); 35 | 36 | $chosenChecks = $this->choice('Which checks should be performed?', $checkNames, 0, null, true); 37 | 38 | $chosenChecks = $this->determineChecks($chosenChecks, $checkNames); 39 | 40 | if ($this->determineHostModelClass()::where('name', $hostName)->first()) { 41 | throw new InvalidArgumentException("Host `{$hostName}` already exists"); 42 | } 43 | 44 | $this->determineHostModelClass()::create([ 45 | 'name' => $hostName, 46 | 'ssh_user' => $sshUser, 47 | 'port' => $port, 48 | 'ip' => $ip, 49 | ])->checks()->saveMany(collect($chosenChecks)->map(function (string $checkName) { 50 | $checkModel = $this->determineCheckModelClass(); 51 | 52 | return new $checkModel([ 53 | 'type' => $checkName, 54 | 'status' => CheckStatus::NOT_YET_CHECKED, 55 | 'custom_properties' => [], 56 | ]); 57 | })); 58 | 59 | $this->info("Host `{$hostName}` added"); 60 | } 61 | 62 | protected function determineChecks(array $chosenChecks, array $checkNames): array 63 | { 64 | if (in_array(static::$allChecksLabel, $chosenChecks)) { 65 | return $this->getAllCheckNames(); 66 | } 67 | 68 | return array_diff($chosenChecks, [static::$allChecksLabel]); 69 | } 70 | 71 | protected function getAllCheckNames(): array 72 | { 73 | return array_keys(config('server-monitor.checks')); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Commands/BaseCommand.php: -------------------------------------------------------------------------------- 1 | setOutput($this); 17 | 18 | return parent::run($input, $output); 19 | } 20 | 21 | public function determineHostModelClass() 22 | { 23 | return HostRepository::determineHostModel(); 24 | } 25 | 26 | public function determineCheckModelClass() 27 | { 28 | return CheckRepository::determineCheckModel(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Commands/DeleteHost.php: -------------------------------------------------------------------------------- 1 | argument('name'); 15 | 16 | $host = $this->determineHostModelClass()::where('name', $name)->first(); 17 | 18 | if (! $host) { 19 | return $this->error("Host with name `{$name}` not found."); 20 | } 21 | 22 | if (! $this->confirm("Are you sure you wish to delete `{$name}`?")) { 23 | return; 24 | } 25 | 26 | $host->delete(); 27 | 28 | $this->info("Host `{$name}` was deleted!"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Commands/DumpChecks.php: -------------------------------------------------------------------------------- 1 | getHostsWithChecks(), JSON_PRETTY_PRINT).PHP_EOL; 19 | 20 | file_put_contents($this->argument('path'), $jsonEncodedHosts); 21 | } 22 | 23 | protected function getHostsWithChecks(): array 24 | { 25 | return HostRepository::all() 26 | ->map(function (Host $host) { 27 | return array_filter([ 28 | 'name' => $host->name, 29 | 'ssh_user' => $host->ssh_user, 30 | 'port' => $host->port, 31 | 'ip' => $host->ip, 32 | 'checks' => $host->checks 33 | ->map( 34 | function (Check $check) { 35 | return $check->type; 36 | } 37 | ) 38 | ->toArray(), 39 | ]); 40 | }) 41 | ->toArray(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Commands/ListChecks.php: -------------------------------------------------------------------------------- 1 | determineHostModelClass()::count() === 0) { 19 | return $this->info('There are no hosts configured'); 20 | } 21 | 22 | $this->unhealthyChecks(); 23 | 24 | $this->healthyChecks(); 25 | } 26 | 27 | protected function unhealthyChecks() 28 | { 29 | $this->tableWithTitle( 30 | 'Unhealthy checks', 31 | ['Host', 'Check', 'Status', 'Message', 'Last checked', 'Next check'], 32 | $this->getTableRows($this->determineCheckModelClass()::unhealthy()->get()) 33 | ); 34 | } 35 | 36 | protected function healthyChecks() 37 | { 38 | $this->tableWithTitle( 39 | 'Healthy checks', 40 | ['Host', 'Check', 'Message', 'Status', 'Last checked', 'Next check'], 41 | $this->getTableRows(self::determineCheckModelClass()::healthy()->get()) 42 | ); 43 | } 44 | 45 | protected function tableWithTitle(string $title, array $header, array $rows) 46 | { 47 | if (count($rows) === 0) { 48 | return; 49 | } 50 | 51 | $this->info($title); 52 | $this->info('================'); 53 | $this->table($header, $rows); 54 | $this->comment(''); 55 | } 56 | 57 | protected function getTableRows(Collection $checks): array 58 | { 59 | if ($hostName = $this->option('host')) { 60 | $checks = $checks->filter(function (Check $check) use ($hostName) { 61 | return $check->host->name === $hostName; 62 | }); 63 | } 64 | 65 | if ($checkType = $this->option('check')) { 66 | $checks = $checks->filter(function (Check $check) use ($checkType) { 67 | return $check->type === $checkType; 68 | }); 69 | } 70 | 71 | return $checks 72 | ->map(function (Check $check) { 73 | return [ 74 | 'name' => $check->host->name, 75 | 'check' => $check->type, 76 | 'last_run_message' => $check->last_run_message, 77 | 'status' => $check->status_as_emoji, 78 | 'last_checked' => $check->getLatestRunDiffAttribute(), 79 | 'next_check' => $check->getNextRunDiffAttribute(), 80 | ]; 81 | }) 82 | ->toArray(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Commands/ListHosts.php: -------------------------------------------------------------------------------- 1 | determineHostModelClass()::count() === 0) { 20 | return $this->info('There are no hosts configured'); 21 | } 22 | 23 | $this->table( 24 | ['Host', 'Health', 'Checks'], 25 | $this->getTableRows($this->determineHostModelClass()::all()) 26 | ); 27 | } 28 | 29 | protected function getTableRows(Collection $hosts): array 30 | { 31 | if ($hostName = $this->option('host')) { 32 | $hosts = $hosts->filter(function (Host $host) use ($hostName) { 33 | return $host->name === $hostName; 34 | }); 35 | } 36 | 37 | return $hosts 38 | ->map(function (Host $host) { 39 | return [ 40 | 'name' => $host->name, 41 | 'health' => $host->health_as_emoji, 42 | 'checks' => $this->getChecksSummary($host, $this->option('check')), 43 | ]; 44 | }) 45 | ->toArray(); 46 | } 47 | 48 | protected function getChecksSummary(Host $host, ?string $typeFilter): string 49 | { 50 | return $host->checks 51 | ->filter(function (Check $check) use ($typeFilter) { 52 | if (is_null($typeFilter)) { 53 | return true; 54 | } 55 | 56 | return $check->type === $typeFilter; 57 | }) 58 | ->map(function (Check $check) { 59 | return $check->summary; 60 | }) 61 | ->implode(PHP_EOL); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Commands/RunChecks.php: -------------------------------------------------------------------------------- 1 | info('Start running '.count($checks).' checks...'); 18 | 19 | $checks->runAll(); 20 | 21 | $this->info('All done!'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Commands/SyncFile.php: -------------------------------------------------------------------------------- 1 | argument('path')); 19 | 20 | $hostsInFile = collect(json_decode($json, true)); 21 | 22 | $this->createOrUpdateHostsFromFile($hostsInFile); 23 | 24 | $this->deleteMissingHosts($hostsInFile); 25 | } 26 | 27 | protected function createOrUpdateHostsFromFile($hostsInFile) 28 | { 29 | $hostsInFile->each(function ($hostAttributes) { 30 | $host = $this->createOrUpdateHost($hostAttributes); 31 | 32 | $this->syncChecks($host, $hostAttributes['checks']); 33 | }); 34 | 35 | $this->info("Synced {$hostsInFile->count()} host(s) to database"); 36 | } 37 | 38 | protected function deleteMissingHosts($hostsInFile) 39 | { 40 | if (! $this->option('delete-missing')) { 41 | return; 42 | } 43 | 44 | $this->determineHostModelClass()::all() 45 | ->reject(function (Host $host) use ($hostsInFile) { 46 | return $hostsInFile->contains('name', $host->name); 47 | }) 48 | ->each(function (Host $host) { 49 | $this->comment("Deleted host `{$host->name}` from database because was not found in hosts file"); 50 | $host->delete(); 51 | }); 52 | } 53 | 54 | protected function createOrUpdateHost(array $hostAttributes): Host 55 | { 56 | unset($hostAttributes['checks']); 57 | 58 | return tap($this->determineHostModelClass()::firstOrNew([ 59 | 'name' => $hostAttributes['name'], 60 | ]), function (Host $hostModel) use ($hostAttributes) { 61 | $hostModel 62 | ->fill($hostAttributes) 63 | ->save(); 64 | }); 65 | } 66 | 67 | protected function syncChecks(Host $host, array $checkTypes): Host 68 | { 69 | $this->removeChecksNotInArray($host, $checkTypes); 70 | 71 | $this->addChecksFromArray($host, $checkTypes); 72 | 73 | return $host; 74 | } 75 | 76 | protected function removeChecksNotInArray(Host $host, array $checkTypes) 77 | { 78 | $host->checks 79 | ->reject(function (Check $check) use ($checkTypes) { 80 | return in_array($check->type, $checkTypes); 81 | }) 82 | ->each(function (Check $check) use ($host) { 83 | $this->comment("Deleted `{$check->type}` from host `{$host->name}` (not found in hosts file)"); 84 | 85 | return $check->delete(); 86 | }); 87 | } 88 | 89 | protected function addChecksFromArray(Host $host, array $checkTypes) 90 | { 91 | collect($checkTypes) 92 | ->reject(function (string $checkType) use ($host) { 93 | return $host->hasCheckType($checkType); 94 | }) 95 | ->each(function (string $checkType) use ($host) { 96 | $host->checks()->create(['type' => $checkType]); 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Events/CheckFailed.php: -------------------------------------------------------------------------------- 1 | check = $check; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidCheckDefinition.php: -------------------------------------------------------------------------------- 1 | id}` has an unknown type `{$check->type}`. Valid values are {$validValues}"); 15 | } 16 | 17 | public static function definitionClassDoesNotExist(Check $check, string $definitionClass) 18 | { 19 | return new static("The definition class {$definitionClass} specified in the configfile as `{$check->type}` does not exist"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidConfiguration.php: -------------------------------------------------------------------------------- 1 | $method(...$arguments); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HostRepository.php: -------------------------------------------------------------------------------- 1 | get(); 15 | 16 | return $hosts; 17 | } 18 | 19 | protected static function query(): Builder 20 | { 21 | $modelClass = static::determineHostModel(); 22 | 23 | return $modelClass::query(); 24 | } 25 | 26 | /** 27 | * Determine the host model class name. 28 | * 29 | * @return string 30 | * 31 | * @throws \Spatie\ServerMonitor\Exceptions\InvalidConfiguration 32 | */ 33 | public static function determineHostModel(): string 34 | { 35 | $hostModel = config('server-monitor.host_model') ?? Host::class; 36 | 37 | if (! is_a($hostModel, Host::class, true)) { 38 | throw InvalidConfiguration::hostModelIsNotValid($hostModel); 39 | } 40 | 41 | return $hostModel; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Manipulators/Manipulator.php: -------------------------------------------------------------------------------- 1 | 'array', 32 | 'last_run_output' => 'array', 33 | 'last_ran_at' => 'datetime', 34 | 'next_check_at' => 'datetime', 35 | 'started_throttling_failing_notifications_at' => 'datetime', 36 | ]; 37 | 38 | public function host(): BelongsTo 39 | { 40 | return $this->belongsTo(config('server-monitor.host_model', Host::class)); 41 | } 42 | 43 | public function scopeHealthy($query) 44 | { 45 | return $query->where('status', CheckStatus::SUCCESS); 46 | } 47 | 48 | public function scopeUnhealthy($query) 49 | { 50 | return $query->where('status', '!=', CheckStatus::SUCCESS); 51 | } 52 | 53 | public function scopeEnabled(Builder $query) 54 | { 55 | $query->where('enabled', 1); 56 | } 57 | 58 | public function shouldRun(): bool 59 | { 60 | if (! $this->enabled) { 61 | return false; 62 | } 63 | 64 | if (is_null($this->last_ran_at)) { 65 | return true; 66 | } 67 | 68 | return ! $this->last_ran_at 69 | ->addMinutes($this->next_run_in_minutes) 70 | ->isFuture(); 71 | } 72 | 73 | public function getDefinition(): CheckDefinition 74 | { 75 | if (! $definitionClass = config("server-monitor.checks.{$this->type}")) { 76 | throw InvalidCheckDefinition::unknownCheckType($this); 77 | } 78 | 79 | if (! class_exists($definitionClass)) { 80 | throw InvalidCheckDefinition::definitionClassDoesNotExist($this, $definitionClass); 81 | } 82 | 83 | return app($definitionClass)->setCheck($this); 84 | } 85 | 86 | public function handleFinishedProcess() 87 | { 88 | $originalStatus = $this->status; 89 | 90 | $this->getDefinition()->determineResult($this->getProcess()); 91 | 92 | $this->scheduleNextRun(); 93 | 94 | if ($this->shouldFireRestoredEvent($originalStatus, $this->status)) { 95 | event(new CheckRestored($this)); 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | protected function shouldFireRestoredEvent(?string $originalStatus, ?string $newStatus) 102 | { 103 | if (! in_array($originalStatus, [CheckStatus::FAILED, CheckStatus::WARNING])) { 104 | return false; 105 | } 106 | 107 | return $newStatus === CheckStatus::SUCCESS; 108 | } 109 | 110 | protected function scheduleNextRun() 111 | { 112 | $this->last_ran_at = Carbon::now(); 113 | 114 | $this->next_run_in_minutes = $this->getDefinition()->performNextRunInMinutes(); 115 | $this->save(); 116 | 117 | return $this; 118 | } 119 | 120 | public function hasStatus(string $status): bool 121 | { 122 | return $this->status === $status; 123 | } 124 | 125 | public function storeProcessOutput(Process $process) 126 | { 127 | $this->last_run_output = [ 128 | 'output' => $process->getOutput(), 129 | 'error_output' => $process->getErrorOutput(), 130 | 'exit_code' => $process->getExitCode(), 131 | 'exit_code_text' => $process->getExitCodeText(), 132 | ]; 133 | 134 | $this->save(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Models/Concerns/HandlesCheckResult.php: -------------------------------------------------------------------------------- 1 | status = CheckStatus::SUCCESS; 16 | $this->last_run_message = $message; 17 | 18 | $this->save(); 19 | 20 | event(new CheckSucceeded($this)); 21 | ConsoleOutput::info($this->host->name.": check `{$this->type}` succeeded"); 22 | 23 | return $this; 24 | } 25 | 26 | public function warn(string $warningMessage = '') 27 | { 28 | $this->status = CheckStatus::WARNING; 29 | $this->last_run_message = $warningMessage; 30 | 31 | $this->save(); 32 | 33 | event(new CheckWarning($this)); 34 | 35 | ConsoleOutput::info($this->host->name.": check `{$this->type}` issued warning"); 36 | 37 | return $this; 38 | } 39 | 40 | public function fail(string $failureReason = '') 41 | { 42 | $this->status = CheckStatus::FAILED; 43 | $this->last_run_message = $failureReason; 44 | 45 | $this->save(); 46 | 47 | event(new CheckFailed($this)); 48 | 49 | ConsoleOutput::error($this->host->name.": check `{$this->type}` failed"); 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Models/Concerns/HasCustomProperties.php: -------------------------------------------------------------------------------- 1 | custom_properties, $propertyName); 12 | } 13 | 14 | /** 15 | * @param string $propertyName 16 | * @param mixed $default 17 | * 18 | * @return mixed 19 | */ 20 | public function getCustomProperty(string $propertyName, $default = null) 21 | { 22 | return Arr::get($this->custom_properties, $propertyName, $default); 23 | } 24 | 25 | /** 26 | * @param string $name 27 | * @param mixed $value 28 | * 29 | * @return $this 30 | */ 31 | public function setCustomProperty(string $name, $value) 32 | { 33 | $customProperties = $this->custom_properties; 34 | 35 | Arr::set($customProperties, $name, $value); 36 | 37 | $this->custom_properties = $customProperties; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * @param string $name 44 | * 45 | * @return $this 46 | */ 47 | public function forgetCustomProperty(string $name) 48 | { 49 | $customProperties = $this->custom_properties; 50 | 51 | Arr::forget($customProperties, $name); 52 | 53 | $this->custom_properties = $customProperties; 54 | 55 | return $this; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Models/Concerns/HasProcess.php: -------------------------------------------------------------------------------- 1 | once("process.{$this->id}", function () { 13 | $process = Process::fromShellCommandline($this->getProcessCommand()); 14 | 15 | $process->setTimeout($this->getDefinition()->timeoutInSeconds()); 16 | 17 | $manipulator = app(Manipulator::class); 18 | 19 | return $manipulator->manipulateProcess($process, $this); 20 | }); 21 | } 22 | 23 | public function getProcessCommand(): string 24 | { 25 | $delimiter = 'EOF-LARAVEL-SERVER-MONITOR'; 26 | 27 | $definition = $this->getDefinition(); 28 | 29 | $portArgument = empty($this->host->port) ? '' : "-p {$this->host->port}"; 30 | 31 | $sshCommandPrefix = config('server-monitor.ssh_command_prefix'); 32 | $sshCommandSuffix = config('server-monitor.ssh_command_suffix'); 33 | 34 | $result = 'ssh'; 35 | if ($sshCommandPrefix) { 36 | $result .= ' '.$sshCommandPrefix; 37 | } 38 | $result .= ' '.$this->getTarget(); 39 | if ($portArgument) { 40 | $result .= ' '.$portArgument; 41 | } 42 | if ($sshCommandSuffix) { 43 | $result .= ' '.$sshCommandSuffix; 44 | } 45 | $result .= " 'bash -se <<$delimiter".PHP_EOL 46 | .'set -e'.PHP_EOL 47 | .$definition->command().PHP_EOL 48 | .$delimiter."'"; 49 | 50 | return $result; 51 | } 52 | 53 | protected function getTarget(): string 54 | { 55 | $target = empty($this->host->ip) 56 | ? $this->host->name 57 | : $this->host->ip; 58 | 59 | if ($this->host->ssh_user) { 60 | $target = $this->host->ssh_user.'@'.$target; 61 | } 62 | 63 | return $target; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Models/Concerns/ThrottlesFailingNotifications.php: -------------------------------------------------------------------------------- 1 | started_throttling_failing_notifications_at)) { 12 | return false; 13 | } 14 | 15 | $throttleDuration = $this->getDefinition()->throttleFailingNotificationsForMinutes(); 16 | 17 | $throttlePeriodEnd = $this->started_throttling_failing_notifications_at->copy()->addMinutes($throttleDuration); 18 | 19 | return $throttlePeriodEnd->isFuture(); 20 | } 21 | 22 | public function stopThrottlingFailedNotifications() 23 | { 24 | $this->started_throttling_failing_notifications_at = null; 25 | 26 | $this->save(); 27 | } 28 | 29 | public function startThrottlingFailedNotifications() 30 | { 31 | $this->started_throttling_failing_notifications_at = Carbon::now(); 32 | 33 | $this->save(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Models/Enums/CheckStatus.php: -------------------------------------------------------------------------------- 1 | 'array', 20 | ]; 21 | 22 | public $guarded = []; 23 | 24 | public function checks(): HasMany 25 | { 26 | return $this->hasMany(config('server-monitor.check_model', Check::class)); 27 | } 28 | 29 | public function getEnabledChecksAttribute(): Collection 30 | { 31 | return $this->checks()->enabled()->get(); 32 | } 33 | 34 | public function isHealthy(): bool 35 | { 36 | return $this->status === HostHealth::HEALTHY; 37 | } 38 | 39 | public function isUnhealthy(): bool 40 | { 41 | return $this->status === HostHealth::UNHEALTHY; 42 | } 43 | 44 | public function hasWarning(): bool 45 | { 46 | return $this->status === HostHealth::WARNING; 47 | } 48 | 49 | public function getStatusAttribute(): string 50 | { 51 | if ($this->enabled_checks->count() === 0) { 52 | return HostHealth::WARNING; 53 | } 54 | 55 | if ($this->enabled_checks->contains->hasStatus(CheckStatus::FAILED)) { 56 | return HostHealth::UNHEALTHY; 57 | } 58 | 59 | if ($this->enabled_checks->every->hasStatus(CheckStatus::SUCCESS)) { 60 | return HostHealth::HEALTHY; 61 | } 62 | 63 | return HostHealth::WARNING; 64 | } 65 | 66 | public function hasCheckType(string $type): bool 67 | { 68 | return $this->checks->contains(function (Check $check) use ($type) { 69 | return $check->type === $type; 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Models/Presenters/CheckPresenter.php: -------------------------------------------------------------------------------- 1 | status === CheckStatus::SUCCESS) { 12 | return '✅'; 13 | } 14 | 15 | if ($this->status === CheckStatus::FAILED) { 16 | return '❌'; 17 | } 18 | 19 | if ($this->status === CheckStatus::WARNING) { 20 | return '⚠️'; 21 | } 22 | 23 | if ($this->status === CheckStatus::NOT_YET_CHECKED) { 24 | return ''; 25 | } 26 | 27 | if (is_null($this->status)) { 28 | return '❓'; 29 | } 30 | 31 | return ''; 32 | } 33 | 34 | public function getSummaryAttribute(): string 35 | { 36 | return "{$this->status_as_emoji} {$this->type}: {$this->last_run_message}"; 37 | } 38 | 39 | public function getLatestRunDiffAttribute(): string 40 | { 41 | if (! $this->last_ran_at) { 42 | return 'Did not run yet'; 43 | } 44 | 45 | return $this->last_ran_at->diffForHumans(); 46 | } 47 | 48 | public function getNextRunDiffAttribute(): string 49 | { 50 | if (! $this->next_run_in_minutes) { 51 | return 'As soon as possible'; 52 | } 53 | 54 | if (! $this->last_ran_at) { 55 | return 'As soon as possible'; 56 | } 57 | 58 | $nextRun = $this->last_ran_at->addMinutes($this->next_run_in_minutes); 59 | 60 | if ($nextRun->isPast()) { 61 | return 'As soon as possible'; 62 | } 63 | 64 | return $this->last_ran_at->addMinutes($this->next_run_in_minutes)->diffForHumans(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Models/Presenters/HostPresenter.php: -------------------------------------------------------------------------------- 1 | isHealthy()) { 12 | return '✅'; 13 | } 14 | 15 | if ($this->isUnhealthy()) { 16 | return '❌'; 17 | } 18 | 19 | if ($this->hasWarning()) { 20 | return '⚠️'; 21 | } 22 | 23 | throw new Exception("Could not determine health emoji for host `{$this->id}`"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Notifications/BaseNotification.php: -------------------------------------------------------------------------------- 1 | event->check; 24 | } 25 | 26 | protected function getMessageText(): ?string 27 | { 28 | return ucfirst($this->getCheck()->last_run_message); 29 | } 30 | 31 | protected function getSubject(): string 32 | { 33 | return "{$this->getCheck()->type} on {$this->getCheck()->host->name}"; 34 | } 35 | 36 | abstract public function shouldSend(): bool; 37 | } 38 | -------------------------------------------------------------------------------- /src/Notifications/EventHandler.php: -------------------------------------------------------------------------------- 1 | config = $config; 21 | } 22 | 23 | public function subscribe(Dispatcher $events) 24 | { 25 | $events->listen($this->allEventClasses(), function ($event) { 26 | $notification = $this->determineNotification($event); 27 | 28 | if (! $notification) { 29 | return; 30 | } 31 | 32 | if ($this->concernsSuccess($event)) { 33 | $notification->getCheck()->stopThrottlingFailedNotifications(); 34 | } 35 | 36 | if ($notification->shouldSend()) { 37 | $notifiable = $this->determineNotifiable(); 38 | 39 | $notifiable->setEvent($event); 40 | 41 | $notifiable->notify($notification); 42 | } 43 | 44 | if (! $this->concernsSuccess($event) && ! $notification->getCheck()->isThrottlingFailedNotifications()) { 45 | $notification->getCheck()->startThrottlingFailedNotifications(); 46 | } 47 | }); 48 | } 49 | 50 | protected function determineNotifiable() 51 | { 52 | $notifiableClass = $this->config->get('server-monitor.notifications.notifiable'); 53 | 54 | return app($notifiableClass); 55 | } 56 | 57 | protected function determineNotification($event): ?BaseNotification 58 | { 59 | $eventName = class_basename($event); 60 | 61 | $notificationClass = collect($this->config->get('server-monitor.notifications.notifications')) 62 | ->filter(function (array $notificationChannels) { 63 | return count($notificationChannels); 64 | }) 65 | ->keys() 66 | ->first(function ($notificationClass) use ($eventName) { 67 | $notificationName = class_basename($notificationClass); 68 | 69 | return $notificationName === $eventName; 70 | }); 71 | 72 | if ($notificationClass) { 73 | return app($notificationClass)->setEvent($event); 74 | } 75 | 76 | return null; 77 | } 78 | 79 | protected function allEventClasses(): array 80 | { 81 | return [ 82 | CheckSucceeded::class, 83 | CheckRestored::class, 84 | CheckWarning::class, 85 | CheckFailed::class, 86 | ]; 87 | } 88 | 89 | protected function concernsSuccess(Event $event): bool 90 | { 91 | return in_array(get_class($event), [ 92 | CheckSucceeded::class, 93 | CheckRestored::class, 94 | ]); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Notifications/Notifiable.php: -------------------------------------------------------------------------------- 1 | event; 43 | } 44 | 45 | /** 46 | * Set the event for the notification. 47 | * 48 | * @param \Spatie\ServerMonitor\Events\Event $event 49 | * 50 | * @return Notifiable 51 | */ 52 | public function setEvent(\Spatie\ServerMonitor\Events\Event $event): self 53 | { 54 | $this->event = $event; 55 | 56 | return $this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Notifications/Notifications/CheckFailed.php: -------------------------------------------------------------------------------- 1 | error() 28 | ->subject($this->getSubject()) 29 | ->line($this->getMessageText()); 30 | } 31 | 32 | public function toSlack($notifiable) 33 | { 34 | return (new SlackMessage) 35 | ->error() 36 | ->attachment(function (SlackAttachment $attachment) { 37 | $attachment 38 | ->title($this->getSubject()) 39 | ->content($this->getMessageText()) 40 | ->fallback($this->getMessageText()) 41 | ->timestamp(Carbon::now()); 42 | }); 43 | } 44 | 45 | public function setEvent(CheckFailedEvent $event) 46 | { 47 | $this->event = $event; 48 | 49 | return $this; 50 | } 51 | 52 | public function shouldSend(): bool 53 | { 54 | if (! $this->getCheck()->hasStatus(CheckStatus::FAILED)) { 55 | return false; 56 | } 57 | 58 | return ! $this->getCheck()->isThrottlingFailedNotifications(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Notifications/Notifications/CheckRestored.php: -------------------------------------------------------------------------------- 1 | success() 28 | ->subject($this->getSubject()) 29 | ->line($this->getMessageText()); 30 | } 31 | 32 | public function toSlack($notifiable) 33 | { 34 | return (new SlackMessage) 35 | ->success() 36 | ->attachment(function (SlackAttachment $attachment) { 37 | $attachment 38 | ->title($this->getSubject()) 39 | ->content($this->getMessageText()) 40 | ->fallback($this->getMessageText()) 41 | ->timestamp(Carbon::now()); 42 | }); 43 | } 44 | 45 | public function setEvent(CheckRestoredEvent $event) 46 | { 47 | $this->event = $event; 48 | 49 | return $this; 50 | } 51 | 52 | public function shouldSend(): bool 53 | { 54 | return $this->getCheck()->hasStatus(CheckStatus::SUCCESS); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Notifications/Notifications/CheckSucceeded.php: -------------------------------------------------------------------------------- 1 | subject($this->getSubject()) 28 | ->line($this->getMessageText()); 29 | } 30 | 31 | public function toSlack($notifiable) 32 | { 33 | return (new SlackMessage) 34 | ->attachment(function (SlackAttachment $attachment) { 35 | $attachment 36 | ->title($this->getSubject()) 37 | ->content($this->getMessageText()) 38 | ->fallback($this->getMessageText()) 39 | ->timestamp(Carbon::now()); 40 | }); 41 | } 42 | 43 | public function setEvent(CheckSucceededEvent $event) 44 | { 45 | $this->event = $event; 46 | 47 | return $this; 48 | } 49 | 50 | public function shouldSend(): bool 51 | { 52 | return $this->getCheck()->hasStatus(CheckStatus::SUCCESS); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Notifications/Notifications/CheckWarning.php: -------------------------------------------------------------------------------- 1 | error() 28 | ->subject($this->getSubject()) 29 | ->line($this->getMessageText()); 30 | } 31 | 32 | public function toSlack($notifiable) 33 | { 34 | return (new SlackMessage) 35 | ->warning() 36 | ->attachment(function (SlackAttachment $attachment) { 37 | $attachment 38 | ->title($this->getSubject()) 39 | ->content($this->getMessageText()) 40 | ->fallback($this->getMessageText()) 41 | ->timestamp(Carbon::now()); 42 | }); 43 | } 44 | 45 | public function setEvent(CheckWarningEvent $event) 46 | { 47 | $this->event = $event; 48 | 49 | return $this; 50 | } 51 | 52 | public function shouldSend(): bool 53 | { 54 | if (! $this->getCheck()->hasStatus(CheckStatus::WARNING)) { 55 | return false; 56 | } 57 | 58 | return ! $this->getCheck()->isThrottlingFailedNotifications(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ServerMonitorServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 23 | $this->publishes([ 24 | __DIR__.'/../config/server-monitor.php' => config_path('server-monitor.php'), 25 | ], 'config'); 26 | 27 | $this->publishesMigration('CreateHostsTable', 'create_hosts_table', 1); 28 | $this->publishesMigration('CreateChecksTable', 'create_checks_table', 2); 29 | } 30 | } 31 | 32 | public function register() 33 | { 34 | $this->mergeConfigFrom(__DIR__.'/../config/server-monitor.php', 'server-monitor'); 35 | 36 | $this->app->bind(Manipulator::class, config('server-monitor.process_manipulator')); 37 | 38 | $this->app['events']->subscribe(EventHandler::class); 39 | 40 | $this->app->bind('command.server-monitor:run-checks', RunChecks::class); 41 | $this->app->bind('command.server-monitor:add-host', AddHost::class); 42 | $this->app->bind('command.server-monitor:delete-host', DeleteHost::class); 43 | $this->app->bind('command.server-monitor:sync-file', SyncFile::class); 44 | $this->app->bind('command.server-monitor:list', ListHosts::class); 45 | $this->app->bind('command.server-monitor:list-checks', ListChecks::class); 46 | $this->app->bind('command.server-monitor:dump-checks', DumpChecks::class); 47 | $this->app->singleton('blink', Blink::class); 48 | 49 | $this->commands([ 50 | 'command.server-monitor:run-checks', 51 | 'command.server-monitor:add-host', 52 | 'command.server-monitor:delete-host', 53 | 'command.server-monitor:sync-file', 54 | 'command.server-monitor:list', 55 | 'command.server-monitor:list-checks', 56 | 'command.server-monitor:dump-checks', 57 | ]); 58 | } 59 | 60 | protected function publishesMigration(string $className, string $fileName, int $timestampSuffix) 61 | { 62 | if (! class_exists($className)) { 63 | $timestamp = (new DateTime())->format('Y_m_d_His').$timestampSuffix; 64 | 65 | $this->publishes([ 66 | __DIR__."/../database/migrations/{$fileName}.php.stub" => database_path('migrations/'.$timestamp."_{$fileName}.php"), 67 | ], 'migrations'); 68 | } 69 | } 70 | } 71 | --------------------------------------------------------------------------------