mirror of
https://github.com/Unitech/pm2.git
synced 2026-02-01 16:57:09 +00:00
Merge remote-tracking branch 'origin/development'
# Conflicts: # README.md
This commit is contained in:
commit
29cccd9437
@ -2,10 +2,10 @@
|
||||
"app_name": "",
|
||||
"logo": "",
|
||||
"intro": "",
|
||||
"branch" : "master",
|
||||
"branch" : "development",
|
||||
"repo_url": "https://github.com/Unitech/pm2",
|
||||
"version_name" : "2.10.4",
|
||||
"tag": "2.10.3",
|
||||
"version_name" : "3.0.0",
|
||||
"tag": "2.10.4",
|
||||
"file": "currentTagChangelog.md",
|
||||
"template": "changelogTemplate.md",
|
||||
"sections": [
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@ package-lock.json
|
||||
*.swp
|
||||
*.swo
|
||||
currentTagChangelog.md
|
||||
joblog-X
|
||||
test/fixtures/path-check*.txt
|
||||
|
||||
@ -4,14 +4,16 @@ node_js:
|
||||
- "4"
|
||||
- "6"
|
||||
- "8"
|
||||
- "0.12"
|
||||
git:
|
||||
depth: 5
|
||||
os:
|
||||
- linux
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install parallel
|
||||
- sudo apt-get install python3
|
||||
- sudo apt-get install php5-cli
|
||||
services:
|
||||
- docker
|
||||
notifications:
|
||||
slack: pm2-nodejs:5Lolyw2LMnwy8fziqOGILQxG
|
||||
install:
|
||||
- npm install
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
- [Without Keymetrics](#without-keymetrics)
|
||||
- [With Keymetrics](#with-keymetrics)
|
||||
|
||||
### Deployment - ecosystem.json
|
||||
### Deployment - ecosystem.config.js
|
||||
|
||||
- [Getting started with deployment](#deployment)
|
||||
- [Deployment options](#deployment-help)
|
||||
|
||||
309
CHANGELOG.md
309
CHANGELOG.md
@ -1,3 +1,312 @@
|
||||
## 3.0.0 ( Wed Jun 20 2018 11:06:21 GMT+0200 (CEST) )
|
||||
|
||||
|
||||
## Breaking changes
|
||||
- merge_logs is now activated by default if not in cluster mode. Logs will not be suffixed by the pm_id if only one app is started
|
||||
([ae02adf6](https://github.com/Unitech/pm2/commit/ae02adf63f70ceb3bf101be968996ca68d9ce277))
|
||||
- Drop support for node 0.12
|
||||
- Drop gracefulReload command
|
||||
- Remove Interactor from PM2 source code
|
||||
- Replace pmx with [pm2-io-apm](https://github.com/keymetrics/pm2-io-apm)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- return the configuration and allow custom conf to override default values
|
||||
([37dc7de1](https://github.com/Unitech/pm2/commit/37dc7de11e930aa4fce6a485e892f11ee714acd6))
|
||||
- add use strict for node 4 compatibility
|
||||
([ba2ee3b1](https://github.com/Unitech/pm2/commit/ba2ee3b1ea9aa5fa665e706b3d49a205eac44d53))
|
||||
- #3605 fix parameters definition, don't use camelcase for properties
|
||||
([c8616276](https://github.com/Unitech/pm2/commit/c8616276e4e08b4d90a742e219372e775bb81098))
|
||||
- #3695 change version check method in order to make it work with alpha/beta versions
|
||||
([052d6c55](https://github.com/Unitech/pm2/commit/052d6c55df0e941e1dd11430bbcbcaa34061a06e))
|
||||
- deprecated warning on isbinaryfile
|
||||
([db09275f](https://github.com/Unitech/pm2/commit/db09275f8e353e257c89e12fed754236b15cee74))
|
||||
- #3688 test adaptation + pm2 serve --port option
|
||||
([f0249684](https://github.com/Unitech/pm2/commit/f0249684bcbfdb75749a516f447c8e8d32020709))
|
||||
- startup script issue 18.04 #3645
|
||||
([ff1a7f31](https://github.com/Unitech/pm2/commit/ff1a7f315bfee38eb9fd9cdd63efcc0d971585f8))
|
||||
- that this - uncache node_modules
|
||||
([294038d7](https://github.com/Unitech/pm2/commit/294038d76272a915e3addc67d3694717a9f7d704))
|
||||
- verify default conf variable via package.json on public module
|
||||
([157b106d](https://github.com/Unitech/pm2/commit/157b106df78af1d28d37bbea069b926de4dceca5))
|
||||
- bug because of const
|
||||
([56f05a90](https://github.com/Unitech/pm2/commit/56f05a900b03fb0c8dd635aede666c7d2f213271))
|
||||
- do not run two pm2 para cmds
|
||||
([3274132b](https://github.com/Unitech/pm2/commit/3274132b866ba5c93d5786e755acbada922f5f1e))
|
||||
- version
|
||||
([3ec178e5](https://github.com/Unitech/pm2/commit/3ec178e577e79730aae02c913301cd905ea8ce52))
|
||||
- re-enable agent tests
|
||||
([e6febcd7](https://github.com/Unitech/pm2/commit/e6febcd70dd0f1e68b74df8563d3046ee3b32b89))
|
||||
- test/display summary
|
||||
([b075e6d0](https://github.com/Unitech/pm2/commit/b075e6d09b09ff371adf045dc5079bb8ef82f1cf))
|
||||
- skip interactor tests
|
||||
([36c4d6bc](https://github.com/Unitech/pm2/commit/36c4d6bca7445b46afc1236dc8ab4b8bf921148b))
|
||||
- remove unused tests
|
||||
([234c6314](https://github.com/Unitech/pm2/commit/234c63143e723a508796bc1d323c7241979bf4c2))
|
||||
- add missing libraries in travis
|
||||
([88fbb845](https://github.com/Unitech/pm2/commit/88fbb84597cee7029ce33f5b7e20e45f5a815b4b))
|
||||
- remove unused variable when trying to use tracing
|
||||
([3aeeba02](https://github.com/Unitech/pm2/commit/3aeeba02f628bf4f19e8d5b93657fd94a6ef0ec7))
|
||||
- remove useless tests from .sh
|
||||
([e0be81c8](https://github.com/Unitech/pm2/commit/e0be81c86c7defb5e7a271edd5cc37f960c6aa69))
|
||||
- conflict
|
||||
([e13f39c9](https://github.com/Unitech/pm2/commit/e13f39c90b6a5e803c59c5424332520564703f5c))
|
||||
- fix bug with interpreter args
|
||||
([b26efa0d](https://github.com/Unitech/pm2/commit/b26efa0d4cd72cf04762df7b7d2eaddc4f4117d2))
|
||||
- improve error message if action has failed
|
||||
([d9f44f17](https://github.com/Unitech/pm2/commit/d9f44f170f115c2d6dfb6a7fe71dc31bd7fb66fb))
|
||||
- use polyfill module for copySync with node 4.x
|
||||
([bc07f43b](https://github.com/Unitech/pm2/commit/bc07f43b115066f6077606df8f59379777f2a917))
|
||||
- improve error message if action has failed
|
||||
([dacc6542](https://github.com/Unitech/pm2/commit/dacc654207cbe494af0d12a3f9f27c3b16541802))
|
||||
- solve empty list when no process and try to update pm2
|
||||
([89511846](https://github.com/Unitech/pm2/commit/8951184688c720ded5b4b46bd5b393c3793f9b03))
|
||||
- #3485 fix issue when there is empty dump file
|
||||
([f2523f6a](https://github.com/Unitech/pm2/commit/f2523f6a6b9d8b61ba6ace7b89a0353bee76360b))
|
||||
- #3456 use homedir() instead of process.env.HOME, make module installation work on windows
|
||||
([1e001732](https://github.com/Unitech/pm2/commit/1e0017325fc8cf658263fb4e02c7bf8912f422b3))
|
||||
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
- add support for openbsd rc.d init scripts
|
||||
([fdeb0c32](https://github.com/Unitech/pm2/commit/fdeb0c327afd91b113b214c4c4de187848f9f1cb))
|
||||
- add kill_retry_time argument
|
||||
([b2cc0031](https://github.com/Unitech/pm2/commit/b2cc003114b44f1a9a31876ee4a2f4cb91e210b3))
|
||||
|
||||
- **bin/pm2**
|
||||
- improve usage
|
||||
([2c310084](https://github.com/Unitech/pm2/commit/2c310084453dd7b1546957e59b1fc7ef964d425b))
|
||||
|
||||
|
||||
|
||||
|
||||
## Refactor
|
||||
- use @pm2/js-api for login/register on pm2.io via CLI
|
||||
([cb6521ac](https://github.com/Unitech/pm2/commit/cb6521ac32f4737c42fc97fef972960bfe16c829))
|
||||
- keymetrics examples
|
||||
([109b331d](https://github.com/Unitech/pm2/commit/109b331ddf37e061d1890ef952f4cd167ce53f64))
|
||||
- faster cli with less require
|
||||
([ee5e6a06](https://github.com/Unitech/pm2/commit/ee5e6a06cbf93f2d1fa7fa022d6bdcad55a39695))
|
||||
- replace fs-extra with node calls
|
||||
([4576b4c9](https://github.com/Unitech/pm2/commit/4576b4c97bc685c9d774018d6b29c918abd7cb8d))
|
||||
- centralize SECRET/PUBLIC/MACHINE_NAME + change some wordings
|
||||
([d0a2a30e](https://github.com/Unitech/pm2/commit/d0a2a30e4110496b178199fb33e026d6402dd00d))
|
||||
- remove test deported to keymetrics-agent
|
||||
([299a52a2](https://github.com/Unitech/pm2/commit/299a52a253d70edcde23cbd7e0c201d492984df4))
|
||||
- parallel test v1
|
||||
([08612de5](https://github.com/Unitech/pm2/commit/08612de5b7893a004ae33ed77fcb2ee3ff7b2251))
|
||||
- e2e test rewrite
|
||||
([2b9ffd4e](https://github.com/Unitech/pm2/commit/2b9ffd4eb493f1ff32c979e3811f4f1fedfae97d))
|
||||
- drop gracefullreload
|
||||
([bb57c76d](https://github.com/Unitech/pm2/commit/bb57c76d4191343925013d4353299092d80732c9))
|
||||
- add node 4.x support
|
||||
([d322dd00](https://github.com/Unitech/pm2/commit/d322dd00de0f527224c027b4fec5e86f12fd69ed))
|
||||
- create alias method instead of modify prototype
|
||||
([6d8f0dfa](https://github.com/Unitech/pm2/commit/6d8f0dfae8106deb2fee0a7ae15b6ca9802a066d))
|
||||
- change safety var to const
|
||||
([047aa494](https://github.com/Unitech/pm2/commit/047aa494d5c4dd4342915766b54d673db0d5cdf1))
|
||||
- drop some 0.x patch
|
||||
([0cab8880](https://github.com/Unitech/pm2/commit/0cab8880ffa362cf27ab7d7b6a64d6b478dce7cd))
|
||||
- remove prototype from API and create method
|
||||
([9552bd61](https://github.com/Unitech/pm2/commit/9552bd61b72692beb620a91765ad440cdf6abefe))
|
||||
- transform API into class
|
||||
([e3831f95](https://github.com/Unitech/pm2/commit/e3831f95c8d71f98e8840da37f7e883727eccd59))
|
||||
- name tests well
|
||||
([c3ccc651](https://github.com/Unitech/pm2/commit/c3ccc651d09ed7291090f516637b75bda99ff71c))
|
||||
- refactor e2e one line parallel
|
||||
([93802711](https://github.com/Unitech/pm2/commit/938027117cdb2f300ee772ab27f008cbe22a4b19))
|
||||
- e2e rename
|
||||
([8a7db95a](https://github.com/Unitech/pm2/commit/8a7db95aabc8437f292af0316cec81ab80ec41f5))
|
||||
- change params
|
||||
([282186f2](https://github.com/Unitech/pm2/commit/282186f24b19b010999f7c7c49750935ef19c190))
|
||||
- parallelize bash test
|
||||
([d4b4375e](https://github.com/Unitech/pm2/commit/d4b4375e16fe7ac463b252702da662d3a21bf8b4))
|
||||
|
||||
|
||||
|
||||
|
||||
## Test
|
||||
- adapt test to new api
|
||||
([7a275e27](https://github.com/Unitech/pm2/commit/7a275e279ea01b1239e9dd8b9cf8e088e407b96d))
|
||||
- refactor before/after
|
||||
([b85ca3ca](https://github.com/Unitech/pm2/commit/b85ca3caa3c68e18f7ce6954cc85e90a9d33efef))
|
||||
- 3 concurrent jobs
|
||||
([472aba34](https://github.com/Unitech/pm2/commit/472aba3499ff2d9d0eb834e819410026b1a44503))
|
||||
- move test
|
||||
([9c973324](https://github.com/Unitech/pm2/commit/9c9733246dbe6afff1b488bc3ba3b6fea3877ea5))
|
||||
- move test
|
||||
([952b7631](https://github.com/Unitech/pm2/commit/952b7631d19e1074ea73cc7a67bbaefe20950603))
|
||||
- fix test with km_link
|
||||
([23fd8ecf](https://github.com/Unitech/pm2/commit/23fd8ecfea9b2bf61359f62a8e6e1a582c3b0d6e))
|
||||
|
||||
|
||||
|
||||
|
||||
## Chore
|
||||
- shorten ecosystem file
|
||||
([992a0452](https://github.com/Unitech/pm2/commit/992a045227aed559e708ac4e6bb3f54beabe48e0))
|
||||
- change motd wording
|
||||
([aa183ba1](https://github.com/Unitech/pm2/commit/aa183ba19d88777d82619aa40499c2661d67879e))
|
||||
- merge master in development
|
||||
([0e4453d9](https://github.com/Unitech/pm2/commit/0e4453d9cc789aa08ee778ff400572337e90d2e3))
|
||||
- keymetrics -> pm2
|
||||
([2c8170c2](https://github.com/Unitech/pm2/commit/2c8170c25e231eb8827bb0944b76c2f4b041d84e))
|
||||
- upgrade all modules + keymetrics-agent -> pm2/agent + increase version enabling v8-compile-cache
|
||||
([53ca18c1](https://github.com/Unitech/pm2/commit/53ca18c12868ab177b60a4edff2ccaa8127e301f))
|
||||
- pm2.io -> @pm2/io
|
||||
([ae098962](https://github.com/Unitech/pm2/commit/ae098962df35eee7f482dc0a514fd29a02a5f4ad))
|
||||
- right names as pm2 maintainers
|
||||
([e8cd7131](https://github.com/Unitech/pm2/commit/e8cd7131a6b9c9d497a2079bcbfc03770a753a06))
|
||||
- add changelog generation into contributing.md
|
||||
([d77bfbc3](https://github.com/Unitech/pm2/commit/d77bfbc3c8929851ee19ea604b2a6481d03771e3))
|
||||
- cache node_modules
|
||||
([81627e94](https://github.com/Unitech/pm2/commit/81627e94c72efa1f4d726e20bbf67f0bbd5c116f))
|
||||
- clone last 5 commits
|
||||
([dad38ed1](https://github.com/Unitech/pm2/commit/dad38ed1bae849147f66e44186cd71c4b9cb022d))
|
||||
- delete old stagnating pmx inside test
|
||||
([36834c2c](https://github.com/Unitech/pm2/commit/36834c2c00d496e04c38abaca30202eb650015c4))
|
||||
- pmx -> pm2.io
|
||||
([adcbebc3](https://github.com/Unitech/pm2/commit/adcbebc3f6419cd97c5ea99f3c3a6789585bda66))
|
||||
- updgrade pmx-2
|
||||
([eeeb2988](https://github.com/Unitech/pm2/commit/eeeb2988f8886e405aea107db3b888fc1fc929f8))
|
||||
- disable legacy test
|
||||
([13723bd9](https://github.com/Unitech/pm2/commit/13723bd938d0e6fb1cbf35f15eabe91c52d87b58))
|
||||
- remove test for pmx alert system
|
||||
([c43414a6](https://github.com/Unitech/pm2/commit/c43414a63438d724b8099eb531ec72bab23b8ca2))
|
||||
- sync from master
|
||||
([3424ee27](https://github.com/Unitech/pm2/commit/3424ee27870feaf62fdf4509cce9015f8b1a8a2e))
|
||||
- add unique id for each process
|
||||
([85a5ee0f](https://github.com/Unitech/pm2/commit/85a5ee0f1fd16da9635fb4b16ddcd8d53aca8224))
|
||||
- use npm install for CI as yarn has issue with npm
|
||||
([52902186](https://github.com/Unitech/pm2/commit/5290218626af815f6cae8173bc78d21881a4dda8))
|
||||
- remove unused dependency
|
||||
([830fc15f](https://github.com/Unitech/pm2/commit/830fc15fad1aee95e65b2681482b03369f1f97d7))
|
||||
- upgrade PM2 to 3.0
|
||||
([4bc2eb4c](https://github.com/Unitech/pm2/commit/4bc2eb4c9a8179b9ae38438e98ce7650a91b64db))
|
||||
- remove unused console.log
|
||||
([33db5084](https://github.com/Unitech/pm2/commit/33db5084814ae7940c90b7f933f9514d28008b78))
|
||||
- wording on error message
|
||||
([c251c8c9](https://github.com/Unitech/pm2/commit/c251c8c97e6f18aae584cac6b7f3c83cf4f2de9c))
|
||||
- revert PR #3496
|
||||
([aae1d55e](https://github.com/Unitech/pm2/commit/aae1d55e410c4dcfbbca83eaabbdf1a65d55f3aa))
|
||||
- fix issue with snapshot command + remove command forceGc
|
||||
([97fd1010](https://github.com/Unitech/pm2/commit/97fd1010d005e59f2411042fa95891f9717fa8b7))
|
||||
- wording on error message
|
||||
([5f78ecbf](https://github.com/Unitech/pm2/commit/5f78ecbf90f9f46a7feb2a169968e86b0ecac91e))
|
||||
- drop 0.12 test on travis
|
||||
([beb6e487](https://github.com/Unitech/pm2/commit/beb6e48787c39c66569141d0fd8d090736114d23))
|
||||
- downgrade promptly
|
||||
([074a7a40](https://github.com/Unitech/pm2/commit/074a7a407a31b4d88442f5834d253d62f4e543b8))
|
||||
- remove coffee and livescript dependencies
|
||||
([13d6565c](https://github.com/Unitech/pm2/commit/13d6565c72e3596d05f87bfc8be15d3ee45fb279))
|
||||
- upgrade module version and engine version
|
||||
([84796956](https://github.com/Unitech/pm2/commit/84796956347ca638750fe89cb5545e2a90a0f2c2))
|
||||
|
||||
|
||||
|
||||
|
||||
## Branchs merged
|
||||
- Merge branch 'development' into chore/dev-cache-node-modules
|
||||
([146c4e11](https://github.com/Unitech/pm2/commit/146c4e113c88e8ade17c7558c8e14cf523a3b2d6))
|
||||
- Merge branch 'development' of https://github.com/Unitech/pm2 into new-agent
|
||||
([3514e7fa](https://github.com/Unitech/pm2/commit/3514e7fac624bb83b4cc22651ebc05385f9c284d))
|
||||
- Merge branch 'development' into master
|
||||
([f5668331](https://github.com/Unitech/pm2/commit/f5668331dbe7346304258317a3b84450f421ed03))
|
||||
- Merge branch 'development' into new-usage-cli
|
||||
([4ae27694](https://github.com/Unitech/pm2/commit/4ae27694e34c4bc6ed389566d71fc5ec48b69652))
|
||||
- Merge branch 'Eywek-improv/agent' into new-agent
|
||||
([3e259dd1](https://github.com/Unitech/pm2/commit/3e259dd1d6bb96ea41897c49f3a84557c00c7dad))
|
||||
- Merge branch 'ecosystem-documentation' of github.com:rmonnier/pm2 into ecosystem-documentation
|
||||
([98348955](https://github.com/Unitech/pm2/commit/98348955a6eb3a9cd524b991bd1dd6ed03d2c857))
|
||||
- Merge branch 'development' into ecosystem-documentation
|
||||
([40157784](https://github.com/Unitech/pm2/commit/40157784a63bcb0e744d4ed56f6c687e28379fdd))
|
||||
- Merge branch 'inspect_mode' of github.com:Unitech/pm2 into inspect_mode
|
||||
([7e1494c7](https://github.com/Unitech/pm2/commit/7e1494c7f7971aaf1f4d00d2ee691c3c41775001))
|
||||
- Merge branch 'development' of github.com:Unitech/pm2 into development
|
||||
([48f81a8b](https://github.com/Unitech/pm2/commit/48f81a8b2f6f0db39edd86083fb369b74845c387))
|
||||
- Merge branch 'development' into master
|
||||
([47e54109](https://github.com/Unitech/pm2/commit/47e5410987ab3d824a34c062d70c24ab686e57db))
|
||||
- Merge branch 'development' into module_install_windows
|
||||
([7b82fb91](https://github.com/Unitech/pm2/commit/7b82fb916ed453c1c263bae43c962f6a5294d810))
|
||||
- Merge branch 'development' into module_install_windows
|
||||
([80b0495f](https://github.com/Unitech/pm2/commit/80b0495f63d1224b850af4b14cdeb055e3fef50b))
|
||||
|
||||
|
||||
|
||||
|
||||
## Pull requests merged
|
||||
- Merge pull request #3726 from soyuka/fix-list
|
||||
([0255c5a6](https://github.com/Unitech/pm2/commit/0255c5a6ab1b8a8f609d2183d998695b8c42838d))
|
||||
- Merge pull request #3725 from soyuka/fix-list
|
||||
([a39eb4f8](https://github.com/Unitech/pm2/commit/a39eb4f806e87565f53758a19f0ee289b6489b67))
|
||||
- Merge pull request #3718 from AaronM04/openbsd-init-script
|
||||
([85458261](https://github.com/Unitech/pm2/commit/85458261d2673c609cb252d64ad4dfbaa466d848))
|
||||
- Merge pull request #3721 from Unitech/io_conf
|
||||
([70ec1f81](https://github.com/Unitech/pm2/commit/70ec1f81eae089f75e82723fde7b0b3926d0a9bc))
|
||||
- Merge pull request #3716 from Unitech/io_conf
|
||||
([0bc000b9](https://github.com/Unitech/pm2/commit/0bc000b9aae7dd37b456bc2d4fbc9eb4a9f047ef))
|
||||
- Merge pull request #3714 from Unitech/definition
|
||||
([d8cff0de](https://github.com/Unitech/pm2/commit/d8cff0dec5160a620d1512ff56726c073368d1a4))
|
||||
- Merge pull request #3700 from Unitech/report_error
|
||||
([4b2cad40](https://github.com/Unitech/pm2/commit/4b2cad407b76994e978074a2a3825fe70656304d))
|
||||
- Merge pull request #3670 from Unitech/changelog
|
||||
([4bcbcce1](https://github.com/Unitech/pm2/commit/4bcbcce16ced596f6ca2bab2b77d608a174a7c1a))
|
||||
- Merge pull request #3662 from DanielRuf/chore/dev-cache-node-modules
|
||||
([540590ee](https://github.com/Unitech/pm2/commit/540590ee056b44eed3b688a7b0b16ca78ec82cd9))
|
||||
- Merge pull request #3663 from DanielRuf/chore/dev-clone-last-5-commits
|
||||
([bdf95fc9](https://github.com/Unitech/pm2/commit/bdf95fc997f9ab2995b23668f25f11b6e98b5c47))
|
||||
- Merge pull request #3584 from ngtmuzi/development
|
||||
([33984b64](https://github.com/Unitech/pm2/commit/33984b64a2969ca4a3a5913f0f7da0242b6c5ec1))
|
||||
- Merge pull request #3500 from Unitech/test-parallel
|
||||
([da56c7af](https://github.com/Unitech/pm2/commit/da56c7aff18d3a38b3ad068b22cd75b290bac9d0))
|
||||
- Merge pull request #3539 from KimSeongIl/master
|
||||
([1325704d](https://github.com/Unitech/pm2/commit/1325704d95d324e56b0ebc86aed8137e0d0aa450))
|
||||
- Merge pull request #3556 from N-Nagorny/logs-smart-app-name-cutting
|
||||
([bfddf4fd](https://github.com/Unitech/pm2/commit/bfddf4fdef5ec293119d850cc2532ac5d6490ae3))
|
||||
- Merge pull request #3553 from Unitech/fix_tracing_not_working
|
||||
([9d51fe08](https://github.com/Unitech/pm2/commit/9d51fe0819182339f3a6a4aee7ea603ea3f4dd76))
|
||||
- Merge pull request #3549 from Eywek/new-agent
|
||||
([2f04027b](https://github.com/Unitech/pm2/commit/2f04027b536094d192b399677b3a113102f06b8e))
|
||||
- Merge pull request #3548 from rmonnier/start-ecosystem-default
|
||||
([55412f26](https://github.com/Unitech/pm2/commit/55412f263250395de0085144932cfe06b8c7180d))
|
||||
- Merge pull request #3546 from soyuka/improve-monitor-perf
|
||||
([e4e29233](https://github.com/Unitech/pm2/commit/e4e29233f99db36462a6e8f48eb8ebd3d2fd9fa5))
|
||||
- Merge pull request #3534 from rmonnier/new-usage-cli
|
||||
([5dfba8a4](https://github.com/Unitech/pm2/commit/5dfba8a4491f0bb83f2879915f0c4b164be2552c))
|
||||
- Merge pull request #3542 from rmonnier/default-start-ecosystem
|
||||
([c65595f4](https://github.com/Unitech/pm2/commit/c65595f4a70659e1e0d753e6c28a1fcedf45a91a))
|
||||
- Merge pull request #3545 from rmonnier/default-ecosystem
|
||||
([b3718656](https://github.com/Unitech/pm2/commit/b3718656f630aa54880343d9742534a2a508daec))
|
||||
- Merge pull request #3543 from rmonnier/ecosystem-documentation
|
||||
([a60580a1](https://github.com/Unitech/pm2/commit/a60580a12b4a0066c8df6620317fbc8bf599b0b6))
|
||||
- Merge pull request #3541 from soyuka/development
|
||||
([67e7a015](https://github.com/Unitech/pm2/commit/67e7a015cabaa7b08206a3b1bf9c0399af88f76b))
|
||||
- Merge pull request #3511 from Unitech/inspect_mode
|
||||
([75fb87f8](https://github.com/Unitech/pm2/commit/75fb87f8a1c46a6db8e974b421e857175e69b535))
|
||||
- Merge pull request #3517 from Unitech/polyfill_fs_copy_node4
|
||||
([524f5494](https://github.com/Unitech/pm2/commit/524f54948de5080632d43bb512038d7bd7271619))
|
||||
- Merge pull request #3516 from Unitech/drop_unused_feature
|
||||
([9436f11a](https://github.com/Unitech/pm2/commit/9436f11aeecfc07e77aa9d6b108df4478b43402e))
|
||||
- Merge pull request #3510 from Unitech/dump_refacto
|
||||
([674e4469](https://github.com/Unitech/pm2/commit/674e4469554e6a765bb3d57a3c083e6ab53b20cc))
|
||||
- Merge pull request #3501 from Unitech/refactor_api
|
||||
([9f2c4ca4](https://github.com/Unitech/pm2/commit/9f2c4ca4c9eadf6c7730e3889c72e908cd2d8f5d))
|
||||
- Merge pull request #3496 from rmonnier/master
|
||||
([829cc303](https://github.com/Unitech/pm2/commit/829cc3032b2d61e20f7a2e7d1d819c0ddc0845e8))
|
||||
- Merge pull request #3484 from Unitech/pull_by_name
|
||||
([24d29404](https://github.com/Unitech/pm2/commit/24d294049008a0d01b2bc407b9b2b880d5843fbd))
|
||||
- Merge pull request #3482 from Unitech/mjs_support
|
||||
([ebe7b048](https://github.com/Unitech/pm2/commit/ebe7b0487218557858aaa98527360eca1776b140))
|
||||
- Merge pull request #3495 from Unitech/module_install_windows
|
||||
([e9c625d3](https://github.com/Unitech/pm2/commit/e9c625d3088c71eef4237ecd866b806957c61815))
|
||||
- Merge pull request #3507 from cheapsteak/patch-1
|
||||
([a49287d6](https://github.com/Unitech/pm2/commit/a49287d6a1d22b39270e2d05dee2a17c0ed55797))
|
||||
|
||||
|
||||
|
||||
|
||||
## 2.10.4 ( Thu May 17 2018 14:32:40 GMT+0200 (CEST) )
|
||||
|
||||
|
||||
|
||||
@ -100,3 +100,25 @@ This test files are located in test/bash/*
|
||||
- `$HOME/.pm2/pm2.pid` PM2 pid
|
||||
- `$HOME/.pm2/rpc.sock` Socket file for remote commands
|
||||
- `$HOME/.pm2/pub.sock` Socket file for publishable events
|
||||
|
||||
## Generate changelog
|
||||
|
||||
### requirements
|
||||
|
||||
```
|
||||
npm install git-changelog -g
|
||||
```
|
||||
|
||||
### usage
|
||||
|
||||
Edit .changelogrc
|
||||
Change "version_name" to the next version to release (example 1.1.2).
|
||||
Change "tag" to the latest existing tag (example 1.1.1).
|
||||
|
||||
Run the following command into pm2 directory
|
||||
```
|
||||
git-changelog
|
||||
```
|
||||
|
||||
It will generate currentTagChangelog.md file.
|
||||
Just copy/paste the result into changelog.md
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<img src="https://img.shields.io/badge/node-%3E%3D4-brightgreen.svg" alt="npm version" height="18">
|
||||
</a>
|
||||
|
||||
<a href="https://www.npmjs.com/package/pm2" title="PM2 on NPM">
|
||||
<a href="https://npmcharts.com/compare/pm2?minimal=true" title="PM2 on NPM">
|
||||
<img alt="NPM Downloads" src="https://img.shields.io/npm/dm/pm2.svg?style=flat-square"/>
|
||||
</a>
|
||||
|
||||
@ -234,14 +234,14 @@ $ pm2 reloadLogs # Reload all logs
|
||||
|
||||
PM2 can generates and configure a startup script to keep PM2 and your processes alive at every server restart.
|
||||
|
||||
Supports init systems like: **systemd** (Ubuntu 16, CentOS, Arch), **upstart** (Ubuntu 14/12), **launchd** (MacOSx, Darwin), **rc.d** (FreeBSD).
|
||||
Supports init systems like: **systemd** (Ubuntu 16, CentOS, Arch), **upstart** (Ubuntu 14/12), **launchd** (MacOSx, Darwin), **rc.d** (FreeBSD, OpenBSD).
|
||||
|
||||
```bash
|
||||
# Auto detect init system + generate and setup PM2 boot at server startup
|
||||
$ pm2 startup
|
||||
|
||||
# Manually specify the startup system
|
||||
# Can be: systemd, upstart, launchd, rcd
|
||||
# Can be: systemd, upstart, launchd, rcd, rcd-openbsd
|
||||
$ pm2 startup [platform]
|
||||
|
||||
# Disable and remove PM2 boot at server startup
|
||||
@ -294,7 +294,6 @@ $ pm2 reset [app-name] # Reset all counters
|
||||
$ pm2 stop all # Stop all apps
|
||||
$ pm2 stop 0 # Stop process with id 0
|
||||
$ pm2 restart all # Restart all apps
|
||||
$ pm2 gracefulReload all # Gracefully reload all apps in cluster mode
|
||||
$ pm2 delete all # Kill and delete all apps
|
||||
$ pm2 delete 0 # Delete app with id 0
|
||||
|
||||
|
||||
202
bin/pm2
202
bin/pm2
@ -3,7 +3,7 @@
|
||||
|
||||
var semver = require('semver');
|
||||
|
||||
if (semver.satisfies(process.versions.node, '>= 5.7.0'))
|
||||
if (semver.satisfies(process.versions.node, '>= 6.0.0'))
|
||||
require('v8-compile-cache');
|
||||
|
||||
process.env.PM2_USAGE = 'CLI';
|
||||
@ -33,24 +33,29 @@ if (process.argv.indexOf('-v') > -1) {
|
||||
var pm2 = new PM2();
|
||||
|
||||
commander.version(pkg.version)
|
||||
.option('-v --version', 'get version')
|
||||
.option('-v --version', 'print pm2 version')
|
||||
.option('-s --silent', 'hide all messages', false)
|
||||
.option('-n --name <name>', 'set a name for the process in the process list')
|
||||
.option('-m --mini-list', 'display a compacted list without formatting')
|
||||
.option('--interpreter <interpreter>', 'set a specific interpreter to use for executing app, default: node')
|
||||
.option('--interpreter-args <arguments>', 'set arguments to pass to the interpreter (alias of --node-args)')
|
||||
.option('--node-args <node_args>', 'space delimited arguments to pass to node')
|
||||
.option('-o --output <path>', 'specify log file for stdout')
|
||||
.option('-e --error <path>', 'specify log file for stderr')
|
||||
.option('-l --log [path]', 'specify log file which gathers both stdout and stderr')
|
||||
.option('--log-type <type>', 'specify log output style (raw by default, json optional)')
|
||||
.option('--log-date-format <date format>', 'add custom prefix timestamp to logs')
|
||||
.option('--disable-logs', 'disable all logs storage')
|
||||
.option('--env <environment_name>', 'specify which set of environment variables from ecosystem file must be injected')
|
||||
.option('-a --update-env', 'force an update of the environment with restart/reload (-a <=> apply)')
|
||||
.option('-f --force', 'force actions')
|
||||
.option('--disable-logs', 'do not write logs')
|
||||
.option('-n --name <name>', 'set a <name> for script')
|
||||
.option('-i --instances <number>', 'launch [number] instances (for networked app)(load balanced)')
|
||||
.option('--parallel <number>', 'number of parallel actions (for restart/reload)')
|
||||
.option('-l --log [path]', 'specify entire log file (error and out are both included)')
|
||||
.option('-o --output <path>', 'specify out log file')
|
||||
.option('-e --error <path>', 'specify error log file')
|
||||
.option('-p --pid <pid>', 'specify pid file')
|
||||
.option('-k --kill-timeout <delay>', 'delay before sending final SIGKILL signal to process')
|
||||
.option('--listen-timeout <delay>', 'listen timeout on application reload')
|
||||
.option('--max-memory-restart <memory>', 'specify max memory amount used to autorestart (in octet or use syntax like 100M)')
|
||||
.option('--max-memory-restart <memory>', 'Restart the app if an amount of memory is exceeded (in bytes)')
|
||||
.option('--restart-delay <delay>', 'specify a delay between restarts (in milliseconds)')
|
||||
.option('--env <environment_name>', 'specify environment to get specific env variables (for JSON declaration)')
|
||||
.option('--log-type <type>', 'specify log output style (raw by default, json optional)')
|
||||
.option('-x --execute-command', 'execute a program using fork system')
|
||||
.option('--max-restarts [count]', 'only restart the script COUNT times')
|
||||
.option('-u --user <username>', 'define user when generating startup script')
|
||||
@ -62,19 +67,14 @@ commander.version(pkg.version)
|
||||
.option('--service-name <name>', 'define service name when generating startup script')
|
||||
.option('-c --cron <cron_pattern>', 'restart a running process based on a cron pattern')
|
||||
.option('-w --write', 'write configuration in local folder')
|
||||
.option('--interpreter <interpreter>', 'the interpreter pm2 should use for executing app (bash, python...)')
|
||||
.option('--interpreter-args <arguments>', 'interpret options (alias of --node-args)')
|
||||
.option('--log-date-format <date format>', 'add custom prefix timestamp to logs')
|
||||
.option('--no-daemon', 'run pm2 daemon in the foreground if it doesn\'t exist already')
|
||||
.option('-a --update-env', 'update environment on restart/reload (-a <=> apply)')
|
||||
.option('--source-map-support', 'force source map support')
|
||||
.option('--only <application-name>', 'with json declaration, allow to only act on one application')
|
||||
.option('--disable-source-map-support', 'force source map support')
|
||||
.option('--wait-ready', 'ask pm2 to wait for ready event from your app')
|
||||
.option('--merge-logs', 'merge logs from different instances but keep error and out separated')
|
||||
.option('--watch [paths]', 'watch application folder for changes', function(v, m) { m.push(v); return m;}, [])
|
||||
.option('--ignore-watch <folders|files>', 'folder/files to be ignored watching, should be a specific name or regex - e.g. --ignore-watch="test node_modules \"some scripts\""')
|
||||
.option('--node-args <node_args>', 'space delimited arguments to pass to node in cluster mode - e.g. --node-args="--debug=7001 --trace-deprecation"')
|
||||
.option('--ignore-watch <folders|files>', 'List of paths to ignore (name or regex)')
|
||||
.option('--no-color', 'skip colors')
|
||||
.option('--no-vizion', 'start an app without vizion feature (versioning control)')
|
||||
.option('--no-autorestart', 'start an app without automatic restart')
|
||||
@ -90,38 +90,50 @@ commander.version(pkg.version)
|
||||
.option('--deep-monitoring', 'enable all monitoring tools (equivalent to --v8 --event-loop-inspector --trace)')
|
||||
.usage('[cmd] app');
|
||||
|
||||
commander.on('--help', function() {
|
||||
console.log(' Basic Examples:');
|
||||
console.log('');
|
||||
console.log(' Start an app using all CPUs available + set a name :');
|
||||
console.log(' $ pm2 start app.js -i 0 --name "api"');
|
||||
console.log('');
|
||||
console.log(' Restart the previous app launched, by name :');
|
||||
console.log(' $ pm2 restart api');
|
||||
console.log('');
|
||||
console.log(' Stop the app :');
|
||||
console.log(' $ pm2 stop api');
|
||||
console.log('');
|
||||
console.log(' Restart the app that is stopped :');
|
||||
console.log(' $ pm2 restart api');
|
||||
console.log('');
|
||||
console.log(' Remove the app from the process list :');
|
||||
console.log(' $ pm2 delete api');
|
||||
console.log('');
|
||||
console.log(' Kill daemon pm2 :');
|
||||
console.log(' $ pm2 kill');
|
||||
console.log('');
|
||||
console.log(' Update pm2 :');
|
||||
console.log(' $ npm install pm2@latest -g ; pm2 update');
|
||||
console.log('');
|
||||
console.log(' More examples in https://github.com/Unitech/pm2#usagefeatures');
|
||||
console.log('');
|
||||
console.log(' Deployment help:');
|
||||
console.log('');
|
||||
console.log(' $ pm2 deploy help');
|
||||
console.log('');
|
||||
console.log('');
|
||||
});
|
||||
function displayUsage() {
|
||||
console.log('usage: pm2 [options] <command>')
|
||||
console.log('');
|
||||
console.log('pm2 -h, --help all available commands and options');
|
||||
console.log('pm2 examples display pm2 usage examples');
|
||||
console.log('pm2 <command> -h help on a specific command');
|
||||
console.log('');
|
||||
console.log('Access pm2 files in ~/.pm2');
|
||||
}
|
||||
|
||||
function displayExamples() {
|
||||
console.log('- Start and add a process to the pm2 process list:')
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' $ pm2 start app.js --name app'));
|
||||
console.log('');
|
||||
console.log('- Show the process list:');
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' $ pm2 ls'));
|
||||
console.log('');
|
||||
console.log('- Stop and delete a process from the pm2 process list:');
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' $ pm2 delete app'));
|
||||
console.log('');
|
||||
console.log('- Stop, start and restart a process from the process list:');
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' $ pm2 stop app'));
|
||||
console.log(chalk.cyan(' $ pm2 start app'));
|
||||
console.log(chalk.cyan(' $ pm2 restart app'));
|
||||
console.log('');
|
||||
console.log('- Clusterize an app to all CPU cores available:');
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' $ pm2 start -i max'));
|
||||
console.log('');
|
||||
console.log('- Update pm2 :');
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' $ npm install pm2 -g && pm2 update'));
|
||||
console.log('');
|
||||
console.log('- Install pm2 auto completion:')
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' $ pm2 completion install'))
|
||||
console.log('');
|
||||
console.log('Check the full documentation on https://pm2.io/doc');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (process.argv.indexOf('-s') > -1) {
|
||||
for(var key in console){
|
||||
@ -155,7 +167,7 @@ function checkCompletion(){
|
||||
return data.short;
|
||||
}), data);
|
||||
// array containing commands after which process name should be listed
|
||||
var cmdProcess = ['stop', 'restart', 'scale', 'reload', 'gracefulReload', 'delete', 'reset', 'pull', 'forward', 'backward', 'logs', 'describe', 'desc', 'show'];
|
||||
var cmdProcess = ['stop', 'restart', 'scale', 'reload', 'delete', 'reset', 'pull', 'forward', 'backward', 'logs', 'describe', 'desc', 'show'];
|
||||
|
||||
if (cmdProcess.indexOf(data.prev) > -1) {
|
||||
pm2.list(function(err, list){
|
||||
@ -253,7 +265,7 @@ function patchCommanderArg(cmd) {
|
||||
//
|
||||
// Start command
|
||||
//
|
||||
commander.command('start <file|json|stdin|app_name|pm_id...>')
|
||||
commander.command('start [name|file|ecosystem|id...]')
|
||||
.option('--watch', 'Watch folder for changes')
|
||||
.option('--fresh', 'Rebuild Dockerfile')
|
||||
.option('--daemon', 'Run container in Daemon mode (debug purposes)')
|
||||
@ -280,6 +292,9 @@ commander.command('start <file|json|stdin|app_name|pm_id...>')
|
||||
else {
|
||||
// Commander.js patch
|
||||
cmd = patchCommanderArg(cmd);
|
||||
if (cmd.length === 0) {
|
||||
cmd = [cst.APP_CONF_DEFAULT_FILE];
|
||||
}
|
||||
async.forEachLimit(cmd, 1, function(script, next) {
|
||||
pm2.start(script, commander, next);
|
||||
}, function(err) {
|
||||
@ -329,7 +344,7 @@ commander.command('startOrGracefulReload <json>')
|
||||
//
|
||||
commander.command('stop <id|name|all|json|stdin...>')
|
||||
.option('--watch', 'Stop watching folder for changes')
|
||||
.description('stop a process (to start it again, do pm2 restart <app>)')
|
||||
.description('stop a process')
|
||||
.action(function(param) {
|
||||
async.forEachLimit(param, 1, function(script, next) {
|
||||
pm2.stop(script, next);
|
||||
@ -390,25 +405,24 @@ commander.command('reload <name|all>')
|
||||
pm2.reload(pm2_id, commander);
|
||||
});
|
||||
|
||||
//
|
||||
// Reload process(es)
|
||||
//
|
||||
commander.command('gracefulReload <name|all>')
|
||||
.description('gracefully reload a process. Send a "shutdown" message to close all connections.')
|
||||
.action(function(pm2_id) {
|
||||
pm2.gracefulReload(pm2_id, commander);
|
||||
});
|
||||
|
||||
commander.command('id <name>')
|
||||
.description('get process id by name')
|
||||
.action(function(name) {
|
||||
pm2.getProcessIdByName(name);
|
||||
});
|
||||
|
||||
// Inspect a process
|
||||
commander.command('inspect <name>')
|
||||
.description('inspect a process')
|
||||
.action(function(cmd) {
|
||||
pm2.inspect(cmd, commander);
|
||||
});
|
||||
|
||||
//
|
||||
// Stop and delete a process by name from database
|
||||
//
|
||||
commander.command('delete <name|id|script|all|json|stdin...>')
|
||||
.alias('del')
|
||||
.description('stop and delete a process from pm2 process list')
|
||||
.action(function(name) {
|
||||
if (name == "-") {
|
||||
@ -464,11 +478,11 @@ commander.command('update')
|
||||
/**
|
||||
* Module specifics
|
||||
*/
|
||||
commander.command('install [module|git:// url|json]')
|
||||
commander.command('install <module|git:// url>')
|
||||
.alias('module:install')
|
||||
.option('--v1', 'install module in v1 manner (do not use it)')
|
||||
.option('--safe [time]', 'keep module backup, if new module fail = restore with previous')
|
||||
.description('install or update a module (or a set of modules) and run it forever')
|
||||
.description('install or update a module and run it forever')
|
||||
.action(function(plugin_name, opts) {
|
||||
if (opts.v1)
|
||||
commander.v1 = true;
|
||||
@ -553,41 +567,42 @@ commander.command('report')
|
||||
commander.command('link [secret] [public] [name]')
|
||||
.alias('interact')
|
||||
.option('--info-node [url]', 'set url info node')
|
||||
.description('linking action to keymetrics.io - command can be stop|info|delete|restart')
|
||||
.option('--ws', 'websocket mode')
|
||||
.description('link with the pm2 monitoring dashboard')
|
||||
.action(pm2._pre_interact.bind(pm2));
|
||||
|
||||
commander.command('unlink')
|
||||
.description('linking action to keymetrics.io - command can be stop|info|delete|restart')
|
||||
.description('unlink with the pm2 monitoring dashboard')
|
||||
.action(function() {
|
||||
pm2.unlink();
|
||||
});
|
||||
|
||||
commander.command('unmonitor [name]')
|
||||
.description('unmonitor target process')
|
||||
.action(function(name) {
|
||||
pm2.monitorState('unmonitor', name);
|
||||
});
|
||||
|
||||
commander.command('monitor [name]')
|
||||
.description('monitor target process')
|
||||
.action(function(name) {
|
||||
pm2.monitorState('monitor', name);
|
||||
});
|
||||
|
||||
commander.command('unmonitor [name]')
|
||||
.description('unmonitor target process')
|
||||
.action(function(name) {
|
||||
pm2.monitorState('unmonitor', name);
|
||||
});
|
||||
|
||||
commander.command('open')
|
||||
.description('open dashboard in browser')
|
||||
.description('open the pm2 monitoring dashboard')
|
||||
.action(function(name) {
|
||||
pm2.openDashboard();
|
||||
});
|
||||
|
||||
commander.command('register')
|
||||
.description('create an account on keymetrics')
|
||||
.description('register on pm2 monitoring')
|
||||
.action(function(name) {
|
||||
pm2.registerToKM();
|
||||
});
|
||||
|
||||
commander.command('login')
|
||||
.description('login to keymetrics and link current PM2')
|
||||
.description('use login to link with the pm2 monitoring dashboard')
|
||||
.action(function(name) {
|
||||
pm2.loginToKM();
|
||||
});
|
||||
@ -612,6 +627,15 @@ commander.command('dump')
|
||||
pm2.dump();
|
||||
}));
|
||||
|
||||
//
|
||||
// Delete dump file
|
||||
//
|
||||
commander.command('cleardump')
|
||||
.description('Create empty dump file')
|
||||
.action(failOnUnknown(function() {
|
||||
pm2.clearDump();
|
||||
}));
|
||||
|
||||
//
|
||||
// Save processes to file
|
||||
//
|
||||
@ -645,7 +669,7 @@ commander.command('resurrect')
|
||||
// Set pm2 to startup
|
||||
//
|
||||
commander.command('unstartup [platform]')
|
||||
.description('disable and clear auto startup - [platform]=systemd,upstart,launchd,rcd')
|
||||
.description('disable the pm2 startup hook')
|
||||
.action(function(platform) {
|
||||
pm2.uninstallStartup(platform, commander);
|
||||
});
|
||||
@ -654,7 +678,7 @@ commander.command('unstartup [platform]')
|
||||
// Set pm2 to startup
|
||||
//
|
||||
commander.command('startup [platform]')
|
||||
.description('setup script for pm2 at boot - [platform]=systemd,upstart,launchd,rcd')
|
||||
.description('enable the pm2 startup hook')
|
||||
.action(function(platform) {
|
||||
pm2.startup(platform, commander);
|
||||
});
|
||||
@ -891,15 +915,6 @@ commander.command('backward <name>')
|
||||
pm2.backward(pm2_name);
|
||||
});
|
||||
|
||||
//
|
||||
// Force PM2 to trigger garbage collection
|
||||
//
|
||||
commander.command('gc')
|
||||
.description('force PM2 to trigger garbage collection')
|
||||
.action(function() {
|
||||
pm2.forceGc();
|
||||
});
|
||||
|
||||
//
|
||||
// Perform a deep update of PM2
|
||||
//
|
||||
@ -914,18 +929,27 @@ commander.command('deepUpdate')
|
||||
//
|
||||
commander.command('serve [path] [port]')
|
||||
.alias('expose')
|
||||
.option('--port [port]', 'specify port to listen to')
|
||||
.description('serve a directory over http via port')
|
||||
.action(function (path, port) {
|
||||
pm2.serve(path, port, commander);
|
||||
.action(function (path, port, cmd) {
|
||||
pm2.serve(path, port || cmd.port, commander);
|
||||
});
|
||||
|
||||
commander.command('examples')
|
||||
.description('display pm2 usage examples')
|
||||
.action(() => {
|
||||
console.log(cst.PREFIX_MSG + chalk.grey('pm2 usage examples:\n'));
|
||||
displayExamples();
|
||||
process.exit(cst.SUCCESS_EXIT);
|
||||
})
|
||||
|
||||
//
|
||||
// Catch all
|
||||
//
|
||||
commander.command('*')
|
||||
.action(function() {
|
||||
console.log(cst.PREFIX_MSG + '\nCommand not found');
|
||||
commander.outputHelp();
|
||||
console.log(cst.PREFIX_MSG + 'Command not found\n');
|
||||
displayUsage();
|
||||
// Check if it does not forget to close fds from RPC
|
||||
process.exit(cst.ERROR_EXIT);
|
||||
});
|
||||
@ -935,7 +959,7 @@ commander.command('*')
|
||||
//
|
||||
if (process.argv.length == 2) {
|
||||
commander.parse(process.argv);
|
||||
commander.outputHelp();
|
||||
displayUsage();
|
||||
// Check if it does not forget to close fds from RPC
|
||||
process.exit(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
17
constants.js
17
constants.js
@ -8,7 +8,6 @@ var debug = require('debug')('pm2:conf');
|
||||
var p = require('path');
|
||||
var util = require('util');
|
||||
var chalk = require('chalk');
|
||||
var semver = require('semver');
|
||||
|
||||
/**
|
||||
* Get PM2 path structure
|
||||
@ -28,7 +27,7 @@ var csts = {
|
||||
|
||||
TEMPLATE_FOLDER : p.join(__dirname, 'lib/templates'),
|
||||
|
||||
APP_CONF_DEFAULT_FILE : 'ecosystem.json',
|
||||
APP_CONF_DEFAULT_FILE : 'ecosystem.config.js',
|
||||
APP_CONF_TPL : 'ecosystem.tpl',
|
||||
APP_CONF_TPL_SIMPLE : 'ecosystem-simple.tpl',
|
||||
SAMPLE_CONF_FILE : 'sample-conf.js',
|
||||
@ -55,7 +54,12 @@ var csts = {
|
||||
|
||||
LOW_MEMORY_ENVIRONMENT : process.env.PM2_OPTIMIZE_MEMORY || false,
|
||||
|
||||
KEYMETRICS_ROOT_URL : process.env.KEYMETRICS_NODE || 'root.keymetrics.io',
|
||||
MACHINE_NAME : process.env.INSTANCE_NAME || process.env.MACHINE_NAME,
|
||||
SECRET_KEY : process.env.KEYMETRICS_SECRET || process.env.PM2_SECRET_KEY || process.env.SECRET_KEY,
|
||||
PUBLIC_KEY : process.env.KEYMETRICS_PUBLIC || process.env.PM2_PUBLIC_KEY || process.env.PUBLIC_KEY,
|
||||
KEYMETRICS_ROOT_URL : process.env.KEYMETRICS_NODE || process.env.ROOT_URL || process.env.INFO_NODE || 'root.keymetrics.io',
|
||||
|
||||
|
||||
KEYMETRICS_BANNER : '../lib/motd',
|
||||
KEYMETRICS_UPDATE : '../lib/motd.update',
|
||||
DEFAULT_MODULE_JSON : 'package.json',
|
||||
@ -74,10 +78,7 @@ var csts = {
|
||||
|
||||
// Concurrent actions when doing start/restart/reload
|
||||
CONCURRENT_ACTIONS : (function() {
|
||||
var default_concurrent_actions = 1;
|
||||
if (semver.satisfies(process.versions.node, '>= 4.0.0'))
|
||||
default_concurrent_actions = 2;
|
||||
var concurrent_actions = parseInt(process.env.PM2_CONCURRENT_ACTIONS) || default_concurrent_actions;
|
||||
var concurrent_actions = parseInt(process.env.PM2_CONCURRENT_ACTIONS) || 2;
|
||||
debug('Using %d parallelism (CONCURRENT_ACTIONS)', concurrent_actions);
|
||||
return concurrent_actions;
|
||||
})(),
|
||||
@ -91,7 +92,7 @@ var csts = {
|
||||
WORKER_INTERVAL : process.env.PM2_WORKER_INTERVAL || 30000,
|
||||
KILL_TIMEOUT : process.env.PM2_KILL_TIMEOUT || 1600,
|
||||
PM2_PROGRAMMATIC : typeof(process.env.pm_id) !== 'undefined' || process.env.PM2_PROGRAMMATIC,
|
||||
PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DD HH:mm:ss'
|
||||
PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/*
|
||||
* Example of graceful exit
|
||||
*
|
||||
* $ pm2 gracefulReload all
|
||||
* $ pm2 reload all
|
||||
*/
|
||||
|
||||
process.on('message', function(msg) {
|
||||
|
||||
@ -399,33 +399,6 @@ API.prototype.update = function(cb) {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Graceful Reload an application
|
||||
*
|
||||
* @param {String} process_name Application Name or All
|
||||
* @param {Object} opts Options
|
||||
* @param {Function} cb Callback
|
||||
*/
|
||||
API.prototype.gracefulReload = function(process_name, opts, cb) {
|
||||
var that = this;
|
||||
|
||||
if (typeof(opts) == "function") {
|
||||
cb = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
//Common.printOut(conf.PREFIX_MSG_WARNING + chalk.bold.yellow('Warning gracefulReload will be soon deprecated'));
|
||||
//Common.printOut(conf.PREFIX_MSG_WARNING + chalk.bold.yellow('Use http://pm2.keymetrics.io/docs/usage/signals-clean-restart/ instead'));
|
||||
|
||||
if (Common.isConfigFile(process_name))
|
||||
that._startJson(process_name, commander, 'softReloadProcessId');
|
||||
else {
|
||||
if (opts && !opts.updateEnv)
|
||||
Common.printOut(IMMUTABLE_MSG);
|
||||
that._operate('softReloadProcessId', process_name, opts, cb);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reload an application
|
||||
*
|
||||
|
||||
@ -47,7 +47,7 @@ function fib(n) {
|
||||
}
|
||||
|
||||
|
||||
var axm = require('pmx');
|
||||
var axm = require('@pm2/io');
|
||||
|
||||
axm.action('load:start', function(reply) {
|
||||
fib(50000);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
var axm = require('pmx');
|
||||
var axm = require('@pm2/io');
|
||||
|
||||
axm.action('getEnv', function(reply) {
|
||||
reply(process.env);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
var axm = require('pmx');
|
||||
var axm = require('@pm2/io');
|
||||
|
||||
axm.action('refresh:db', { comment : 'Refresh the database' }, function(reply) {
|
||||
console.log('Refreshing');
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
var axm = require('pmx');
|
||||
var axm = require('@pm2/io');
|
||||
|
||||
setInterval(function() {
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
var pmx = require('pmx').init({ http : true });
|
||||
var probe = pmx.probe();
|
||||
var io = require('@pm2/io').init({ http : true });
|
||||
var probe = io.probe();
|
||||
|
||||
var http = require('http');
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
var axm = require('pmx');
|
||||
var axm = require('@pm2/io');
|
||||
|
||||
var probe = axm.probe();
|
||||
|
||||
@ -17,7 +17,7 @@ http.createServer(function(req, res) {
|
||||
setTimeout(function() {
|
||||
res.end('transaction');
|
||||
}, 1000);
|
||||
}).listen(9010);
|
||||
}).listen(10010);
|
||||
|
||||
setInterval(function() {
|
||||
request(['/user', '/bla', '/user/lol/delete', '/POST/POST'][Math.floor((Math.random() * 4))]);
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
var pmx = require('pmx');
|
||||
var io = require('@pm2/io');
|
||||
var pm2 = require('../..');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var conf = pmx.initModule({
|
||||
var conf = io.initModule({
|
||||
comment : 'This module monitors PM2',
|
||||
errors : true,
|
||||
latency : false,
|
||||
versioning : false,
|
||||
show_module_meta : false,
|
||||
module_type : 'database',
|
||||
pid : pmx.getPID(path.join(process.env.HOME, '.pm2', 'pm2.pid')),
|
||||
|
||||
widget : {
|
||||
theme : ['#111111', '#1B2228', '#807C7C', '#807C7C'],
|
||||
@ -18,7 +17,7 @@ var conf = pmx.initModule({
|
||||
}
|
||||
});
|
||||
|
||||
var probe = pmx.probe();
|
||||
var probe = io.probe();
|
||||
|
||||
var pm2_procs = 0;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
var axm = require('pmx');
|
||||
var axm = require('@pm2/io');
|
||||
|
||||
var probe = axm.probe();
|
||||
|
||||
@ -84,7 +84,7 @@ setInterval(function() {
|
||||
}, 1500);
|
||||
|
||||
|
||||
axm.catchAll();
|
||||
//axm.catchAll();
|
||||
|
||||
axm.action('throw error', function(reply) {
|
||||
setTimeout(function() {
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
|
||||
var Probe = require('pmx').probe();
|
||||
var Probe = require('@pm2/io').probe();
|
||||
|
||||
var counter = 0;
|
||||
|
||||
var metric = Probe.transpose({
|
||||
name : 'data-flow',
|
||||
data : function() {
|
||||
return {
|
||||
a : {
|
||||
b : {
|
||||
data : 'textflow',
|
||||
array : [ 'yes', 'it', 'is' ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// var metric = Probe.transpose({
|
||||
// name : 'data-flow',
|
||||
// data : function() {
|
||||
// return {
|
||||
// a : {
|
||||
// b : {
|
||||
// data : 'textflow',
|
||||
// array : [ 'yes', 'it', 'is' ]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
setInterval(function() {
|
||||
}, 100);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
var pmx = require('pmx');
|
||||
var io = require('@pm2/io');
|
||||
|
||||
pmx.scopedAction('simple test', function(data, emitter) {
|
||||
io.scopedAction('simple test', function(data, emitter) {
|
||||
var i = setInterval(function() {
|
||||
emitter.send('output-stream');
|
||||
}, 100);
|
||||
@ -13,7 +13,7 @@ pmx.scopedAction('simple test', function(data, emitter) {
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
pmx.scopedAction('throwing error', function(data, emitter) {
|
||||
io.scopedAction('throwing error', function(data, emitter) {
|
||||
var i = setInterval(function() {
|
||||
emitter.send('output-stream');
|
||||
}, 100);
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
|
||||
var axm = require('pmx');
|
||||
|
||||
axm.catchAll();
|
||||
var axm = require('@pm2/io');
|
||||
|
||||
setTimeout(function() {
|
||||
console.log('log message from echo auto kill');
|
||||
|
||||
3208
lib/API.js
3208
lib/API.js
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
||||
|
||||
var Password = require('../Interactor/Password.js');
|
||||
var Common = require('../Common.js');
|
||||
var cst = require('../../constants.js');
|
||||
var UX = require('./CliUx');
|
||||
@ -7,7 +6,7 @@ var chalk = require('chalk');
|
||||
var async = require('async');
|
||||
var Configuration = require('../Configuration.js');
|
||||
//@todo double check that imported methods works
|
||||
var InteractorDaemonizer = require('../Interactor/InteractorDaemonizer');
|
||||
var InteractorDaemonizer = require('@pm2/agent/src/InteractorClient');
|
||||
|
||||
module.exports = function(CLI) {
|
||||
|
||||
@ -63,36 +62,6 @@ module.exports = function(CLI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific when setting pm2 password
|
||||
* Used for restricted remote actions
|
||||
* Also alert Interactor that password has been set
|
||||
*/
|
||||
if (key.indexOf('pm2:passwd') > -1) {
|
||||
value = Password.generate(value);
|
||||
Configuration.set(key, value, function(err) {
|
||||
if (err)
|
||||
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
|
||||
InteractorDaemonizer.launchRPC(that._conf, function(err) {
|
||||
if (err) {
|
||||
displayConf('pm2', function() {
|
||||
return cb ? cb(null, {success:true}) : that.exitCli(cst.SUCCESS_EXIT)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
InteractorDaemonizer.rpc.passwordSet(function() {
|
||||
InteractorDaemonizer.disconnectRPC(function() {
|
||||
displayConf('pm2', function() {
|
||||
return cb ? cb(null, {success:true}) : that.exitCli(cst.SUCCESS_EXIT);
|
||||
});
|
||||
});
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value
|
||||
*/
|
||||
|
||||
@ -4,7 +4,6 @@ var exec = require('child_process').exec;
|
||||
var chalk = require('chalk');
|
||||
var util = require('util');
|
||||
var fmt = require('../tools/fmt.js');
|
||||
var vizion = require('vizion');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var cst = require('../../constants.js');
|
||||
@ -148,7 +147,7 @@ function handleExit(CLI, opts, mode) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
vizion.analyze({folder : process.cwd()}, function recur_path(err, meta){
|
||||
require('vizion').analyze({folder : process.cwd()}, function recur_path(err, meta){
|
||||
if (!err && meta.revision) {
|
||||
var commit_id = util.format('#%s(%s) %s',
|
||||
meta.branch,
|
||||
|
||||
@ -162,7 +162,7 @@ Dashboard.init = function() {
|
||||
});
|
||||
|
||||
this.box4 = blessed.text({
|
||||
content: ' left/right: switch boards | up/down/mouse: scroll | Ctrl-C: exit{|} {cyan-fg}{bold}To go further check out https://keymetrics.io/{/} ',
|
||||
content: ' left/right: switch boards | up/down/mouse: scroll | Ctrl-C: exit{|} {cyan-fg}{bold}To go further check out https://pm2.io/{/} ',
|
||||
left: '0%',
|
||||
top: '95%',
|
||||
width: '100%',
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var Deploy = require('pm2-deploy');
|
||||
|
||||
var cst = require('../../constants.js');
|
||||
var Utility = require('../Utility.js');
|
||||
var Common = require('../Common.js');
|
||||
@ -104,7 +102,7 @@ module.exports = function(CLI) {
|
||||
json_conf.deploy[env]['post-deploy'] = 'pm2 startOrRestart ' + file + ' --env ' + env;
|
||||
}
|
||||
|
||||
Deploy.deployForEnv(json_conf.deploy, env, args, function(err, data) {
|
||||
require('pm2-deploy').deployForEnv(json_conf.deploy, env, args, function(err, data) {
|
||||
if (err) {
|
||||
Common.printError('Deploy failed');
|
||||
return cb ? cb(err) : that.exitCli(cst.ERROR_EXIT);
|
||||
|
||||
@ -15,6 +15,7 @@ var fs = require('fs');
|
||||
var fmt = require('../tools/fmt.js');
|
||||
var moment = require('moment');
|
||||
var pkg = require('../../package.json');
|
||||
const semver = require('semver');
|
||||
|
||||
module.exports = function(CLI) {
|
||||
|
||||
@ -38,7 +39,6 @@ module.exports = function(CLI) {
|
||||
*/
|
||||
CLI.prototype.report = function() {
|
||||
var that = this;
|
||||
var semver = require('semver');
|
||||
|
||||
function reporting(cb) {
|
||||
|
||||
@ -108,7 +108,7 @@ module.exports = function(CLI) {
|
||||
}
|
||||
|
||||
that.Client.executeRemote('getVersion', {}, function(err, data) {
|
||||
if (semver.satisfies(data, '>= 2.6.0'))
|
||||
if (semver.satisfies(semver.coerce(data), '>=2.6.0'))
|
||||
reporting();
|
||||
else {
|
||||
Common.printError(cst.PREFIX_MSG_ERR + 'You need to update your Daemon, please type `$ pm2 update`');
|
||||
@ -639,4 +639,27 @@ module.exports = function(CLI) {
|
||||
|
||||
launchMonitor();
|
||||
};
|
||||
|
||||
CLI.prototype.inspect = function(app_name, cb) {
|
||||
const that = this;
|
||||
if(semver.satisfies(process.versions.node, '>= 8.0.0')) {
|
||||
this.trigger(app_name, 'internal:inspect', function (err, res) {
|
||||
|
||||
if(res && res[0]) {
|
||||
if (res[0].data.return === '') {
|
||||
Common.printOut(`Inspect disabled on ${app_name}`);
|
||||
} else {
|
||||
Common.printOut(`Inspect enabled on ${app_name} => go to chrome : chrome://inspect !!!`);
|
||||
}
|
||||
} else {
|
||||
Common.printOut(`Unable to activate inspect mode on ${app_name} !!!`);
|
||||
}
|
||||
|
||||
that.exitCli(cst.SUCCESS_EXIT);
|
||||
});
|
||||
} else {
|
||||
Common.printOut('Inspect is available for node version >=8.x !');
|
||||
that.exitCli(cst.SUCCESS_EXIT);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -6,7 +6,8 @@ var chalk = require('chalk');
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var KMDaemon = require('../Interactor/InteractorDaemonizer');
|
||||
var KMDaemon = require('@pm2/agent/src/InteractorClient');
|
||||
var pkg = require('../../package.json')
|
||||
|
||||
module.exports = function(CLI) {
|
||||
|
||||
@ -45,7 +46,8 @@ module.exports = function(CLI) {
|
||||
KMDaemon.launchAndInteract(that._conf, {
|
||||
secret_key : secret_key || null,
|
||||
public_key : public_key || null,
|
||||
machine_name : machine_name || null
|
||||
machine_name : machine_name || null,
|
||||
pm2_version: pkg.version
|
||||
}, function(err, dt) {
|
||||
if (err) {
|
||||
return cb ? cb(err) : that.exitCli(cst.ERROR_EXIT);
|
||||
@ -74,20 +76,20 @@ module.exports = function(CLI) {
|
||||
//
|
||||
// Interact
|
||||
//
|
||||
CLI.prototype._pre_interact = function(cmd, public_key, machine, info_node) {
|
||||
CLI.prototype._pre_interact = function(cmd, public_key, machine, opts) {
|
||||
var that = this;
|
||||
|
||||
if (cmd == 'stop' || cmd == 'kill') {
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' Stopping agent...');
|
||||
console.log(chalk.cyan('[PM2 agent]') + ' Stopping agent...');
|
||||
that.killInteract(function() {
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' Stopped');
|
||||
console.log(chalk.cyan('[PM2 agent]') + ' Stopped');
|
||||
return process.exit(cst.SUCCESS_EXIT);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cmd == 'info') {
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' Getting agent information...');
|
||||
console.log(chalk.cyan('[PM2 agent]') + ' Getting agent information...');
|
||||
that.interactInfos(function(err, infos) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
@ -104,10 +106,10 @@ module.exports = function(CLI) {
|
||||
try {
|
||||
fs.unlinkSync(cst.INTERACTION_CONF);
|
||||
} catch(e) {
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' No interaction config file found');
|
||||
console.log(chalk.cyan('[PM2 agent]') + ' No interaction config file found');
|
||||
return process.exit(cst.SUCCESS_EXIT);
|
||||
}
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' Agent interaction ended');
|
||||
console.log(chalk.cyan('[PM2 agent]') + ' Agent interaction ended');
|
||||
return process.exit(cst.SUCCESS_EXIT);
|
||||
});
|
||||
return false;
|
||||
@ -118,7 +120,8 @@ module.exports = function(CLI) {
|
||||
public_key : null,
|
||||
secret_key : null,
|
||||
machine_name : null,
|
||||
info_node : null
|
||||
info_node : null,
|
||||
pm2_version: pkg.version
|
||||
}, function(err, dt) {
|
||||
if (err) {
|
||||
Common.printError(err);
|
||||
@ -129,7 +132,7 @@ module.exports = function(CLI) {
|
||||
}
|
||||
|
||||
if (cmd && !public_key) {
|
||||
console.error(chalk.cyan('[Keymetrics.io]') + ' Command [%s] unknown or missing public key', cmd);
|
||||
console.error(chalk.cyan('[PM2 agent]') + ' Command [%s] unknown or missing public key', cmd);
|
||||
return process.exit(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
@ -143,9 +146,23 @@ module.exports = function(CLI) {
|
||||
public_key : public_key,
|
||||
secret_key : cmd,
|
||||
machine_name : machine,
|
||||
info_node : info_node.infoNode || null
|
||||
info_node : opts.infoNode || null,
|
||||
pm2_version: pkg.version
|
||||
}
|
||||
|
||||
if (opts.ws === true && infos) {
|
||||
infos.agent_transport_axon = false
|
||||
infos.agent_transport_websocket = true
|
||||
process.env.AGENT_TRANSPORT_AXON = false
|
||||
process.env.AGENT_TRANSPORT_WEBSOCKET = true
|
||||
}
|
||||
else if (infos) {
|
||||
infos.agent_transport_axon = true
|
||||
infos.agent_transport_websocket = false
|
||||
process.env.AGENT_TRANSPORT_AXON = true
|
||||
process.env.AGENT_TRANSPORT_WEBSOCKET = false
|
||||
}
|
||||
|
||||
KMDaemon.launchAndInteract(that._conf, infos, function(err, dt) {
|
||||
if (err)
|
||||
return that.exitCli(cst.ERROR_EXIT);
|
||||
|
||||
@ -1,248 +0,0 @@
|
||||
var cst = require('../../../constants.js');
|
||||
var Common = require('../../Common.js');
|
||||
var UX = require('../CliUx');
|
||||
var chalk = require('chalk');
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var KMDaemon = require('../../Interactor/InteractorDaemonizer');
|
||||
var KM = require('./kmapi.js');
|
||||
var Table = require('cli-table-redemption');
|
||||
var open = require('../../tools/open.js');
|
||||
var promptly = require('promptly');
|
||||
|
||||
module.exports = function(CLI) {
|
||||
|
||||
CLI.prototype.openDashboard = function() {
|
||||
var that = this;
|
||||
|
||||
KMDaemon.getInteractInfo(this._conf, function(err, data) {
|
||||
if (err) {
|
||||
Common.printError(chalk.bold.white('Agent if offline, type `$ pm2 register` to log in'));
|
||||
return that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
Common.printOut(chalk.bold('Opening Dashboard in Browser...'));
|
||||
open('https://app.keymetrics.io/#/r/' + data.public_key);
|
||||
setTimeout(function() {
|
||||
that.exitCli();
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
|
||||
CLI.prototype.loginToKM = function() {
|
||||
printMotd();
|
||||
return loginPrompt();
|
||||
};
|
||||
|
||||
CLI.prototype.registerToKM = function() {
|
||||
printMotd();
|
||||
|
||||
promptly.confirm(chalk.bold('Do you have a Keymetrics.io account? (y/n)'), function (err, answer) {
|
||||
if (answer == true) {
|
||||
return loginPrompt();
|
||||
}
|
||||
registerPrompt();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Monitor Selectively Processes (auto filter in interaction)
|
||||
* @param String state 'monitor' or 'unmonitor'
|
||||
* @param String target <pm_id|name|all>
|
||||
* @param Function cb callback
|
||||
*/
|
||||
CLI.prototype.monitorState = function(state, target, cb) {
|
||||
var that = this;
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
try {
|
||||
fs.statSync(this._conf.INTERACTION_CONF);
|
||||
} catch(e) {
|
||||
printMotd();
|
||||
return registerPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
Common.printError(cst.PREFIX_MSG_ERR + 'Please specify an <app_name|pm_id>');
|
||||
return cb ? cb(new Error('argument missing')) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
function monitor (pm_id, cb) {
|
||||
// State can be monitor or unmonitor
|
||||
that.Client.executeRemote(state, pm_id, cb);
|
||||
}
|
||||
if (target === 'all') {
|
||||
that.Client.getAllProcessId(function (err, procs) {
|
||||
if (err) {
|
||||
Common.printError(err);
|
||||
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
async.forEachLimit(procs, 1, monitor, function (err, res) {
|
||||
return typeof cb === 'function' ? cb(err, res) : that.speedList();
|
||||
});
|
||||
});
|
||||
} else if (!Number.isInteger(parseInt(target))) {
|
||||
this.Client.getProcessIdByName(target, true, function (err, procs) {
|
||||
if (err) {
|
||||
Common.printError(err);
|
||||
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
async.forEachLimit(procs, 1, monitor, function (err, res) {
|
||||
return typeof cb === 'function' ? cb(err, res) : that.speedList();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
monitor(parseInt(target), function (err, res) {
|
||||
return typeof cb === 'function' ? cb(err, res) : that.speedList();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private Functions
|
||||
*/
|
||||
|
||||
function printMotd() {
|
||||
var dt = fs.readFileSync(path.join(__dirname, 'motd'));
|
||||
console.log(dt.toString());
|
||||
}
|
||||
|
||||
function validateEmail(email) {
|
||||
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
if (re.test(email) == false)
|
||||
throw new Error('Not an email');
|
||||
return email;
|
||||
}
|
||||
|
||||
function validateUsername(value) {
|
||||
if (value.length < 6) {
|
||||
throw new Error('Min length of 6');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
function linkOpenExit(target_bucket) {
|
||||
KMDaemon.launchAndInteract(cst, {
|
||||
public_key : target_bucket.public_id,
|
||||
secret_key : target_bucket.secret_id
|
||||
}, function(err, dt) {
|
||||
open('https://app.keymetrics.io/#/r/' + target_bucket.public_id);
|
||||
setTimeout(function() {
|
||||
process.exit(cst.SUCCESS_EXIT);
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Login on Keymetrics
|
||||
* Link to the only bucket or list bucket for selection
|
||||
* Open Browser
|
||||
*/
|
||||
function loginPrompt(cb) {
|
||||
console.log(chalk.bold('Log in to Keymetrics'));
|
||||
(function retry() {
|
||||
promptly.prompt('Username or Email: ', function(err, username) {
|
||||
promptly.password('Password: ', { replace : '*' }, function(err, password) {
|
||||
KM.loginAndGetAccessToken({ username : username, password: password }, function(err) {
|
||||
if (err) {
|
||||
console.error(chalk.red.bold(err) + '\n');
|
||||
return retry();
|
||||
}
|
||||
KM.getBuckets(function(err, buckets) {
|
||||
if (err) {
|
||||
console.error(chalk.red.bold(err) + '\n');
|
||||
return retry();
|
||||
}
|
||||
|
||||
if (buckets.length > 1) {
|
||||
console.log(chalk.bold('Bucket list'));
|
||||
|
||||
var table = new Table({
|
||||
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
|
||||
head : ['Bucket name', 'Plan type']
|
||||
});
|
||||
|
||||
buckets.forEach(function(bucket) {
|
||||
table.push([bucket.name, bucket.credits.offer_type]);
|
||||
});
|
||||
|
||||
console.log(table.toString());
|
||||
|
||||
(function retryInsertion() {
|
||||
promptly.prompt('Type the bucket you want to link to: ', function(err, bucket_name) {
|
||||
var target_bucket = null;
|
||||
|
||||
buckets.some(function(bucket) {
|
||||
if (bucket.name == bucket_name) {
|
||||
target_bucket = bucket;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (target_bucket == null)
|
||||
return retryInsertion();
|
||||
linkOpenExit(target_bucket);
|
||||
});
|
||||
})();
|
||||
}
|
||||
else {
|
||||
var target_bucket = buckets[0];
|
||||
console.log('Connecting local PM2 to Keymetrics Bucket [%s]', target_bucket.name);
|
||||
|
||||
KMDaemon.launchAndInteract(cst, {
|
||||
public_key : target_bucket.public_id,
|
||||
secret_key : target_bucket.secret_id
|
||||
}, function(err, dt) {
|
||||
linkOpenExit(target_bucket);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
})()
|
||||
}
|
||||
|
||||
/**
|
||||
* Register on Keymetrics
|
||||
* Create Bucket
|
||||
* Auto Link local PM2 to new Bucket
|
||||
* Open Browser for access to monitoring dashboard
|
||||
*/
|
||||
function registerPrompt() {
|
||||
console.log(chalk.bold('Now registering to Keymetrics'));
|
||||
promptly.prompt('Username: ', {
|
||||
validator : validateUsername,
|
||||
retry : true
|
||||
}, function(err, username) {
|
||||
promptly.prompt('Email: ', {
|
||||
validator : validateEmail,
|
||||
retry : true
|
||||
}, function(err, email) {
|
||||
promptly.password('Password: ', { replace : '*' }, function(err, password) {
|
||||
process.stdout.write(chalk.bold('Creating account on Keymetrics..'));
|
||||
var inter = setInterval(function() {
|
||||
process.stdout.write('.');
|
||||
}, 300);
|
||||
KM.fullCreationFlow({
|
||||
email : email,
|
||||
password : password,
|
||||
username : username
|
||||
}, function(err, target_bucket) {
|
||||
clearInterval(inter);
|
||||
if (err) {
|
||||
console.error('\n' + chalk.red.bold(err) + '\n');
|
||||
return registerPrompt();
|
||||
}
|
||||
linkOpenExit(target_bucket);
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
@ -1,162 +0,0 @@
|
||||
var querystring = require('querystring');
|
||||
var https = require('https');
|
||||
var fs = require('fs');
|
||||
var needle = require('needle');
|
||||
var url = require('url');
|
||||
var cst = require('../../../constants.js');
|
||||
|
||||
var KM = function() {
|
||||
this.BASE_URI = 'https://app.keymetrics.io';
|
||||
this.CLIENT_ID = '938758711';
|
||||
this.CB_URI = 'https://app.keymetrics.io';
|
||||
this.ACCESS_TOKEN_FILE = cst.KM_ACCESS_TOKEN;
|
||||
this.access_token = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user_info.username
|
||||
* @param user_info.password
|
||||
* @return promise
|
||||
*/
|
||||
KM.prototype.loginAndGetAccessToken = function (user_info, cb) {
|
||||
var that = this;
|
||||
var URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' +
|
||||
that.CLIENT_ID + '&redirect_uri=' + that.CB_URI;
|
||||
|
||||
needle.get(that.BASE_URI + URL_AUTH, function(err, res) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var cookie = res.cookies;
|
||||
|
||||
needle.post(that.BASE_URI + '/api/oauth/login', user_info, {
|
||||
cookies : cookie
|
||||
}, function(err, resp, body) {
|
||||
if (err) return cb(err);
|
||||
if (body.indexOf('/api/oauth/login') > -1) return cb('Wrong credentials');
|
||||
|
||||
var location = resp.headers.location;
|
||||
var redirect = that.BASE_URI + location;
|
||||
|
||||
needle.get(redirect, {
|
||||
cookies : cookie
|
||||
}, function(err, res) {
|
||||
if (err) return cb(err);
|
||||
var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token;
|
||||
|
||||
needle.post(that.BASE_URI + '/api/oauth/token', {
|
||||
client_id : that.CLIENT_ID,
|
||||
grant_type : 'refresh_token',
|
||||
refresh_token : refresh_token,
|
||||
scope : 'all'
|
||||
}, function(err, res, body) {
|
||||
if (err) return cb(err);
|
||||
that.access_token = body.access_token;
|
||||
return cb(null, body.access_token);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
KM.prototype.getLocalAccessToken = function(cb) {
|
||||
var that = this;
|
||||
|
||||
fs.readFile(that.ACCESS_TOKEN_FILE, function(e, content) {
|
||||
if (e) return cb(e);
|
||||
cb(null, content.toString());
|
||||
});
|
||||
};
|
||||
|
||||
KM.prototype.saveLocalAccessToken = function(access_token, cb) {
|
||||
var that = this;
|
||||
fs.writeFile(that.ACCESS_TOKEN_FILE, access_token, function(e, content) {
|
||||
if (e) return cb(e);
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
KM.prototype.getBuckets = function(cb) {
|
||||
var that = this;
|
||||
|
||||
needle.get(that.BASE_URI + '/api/bucket', {
|
||||
headers : {
|
||||
'Authorization' : 'Bearer ' + that.access_token
|
||||
},
|
||||
json : true
|
||||
}, function(err, res, body) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, body);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user_info.username
|
||||
* @param user_info.password
|
||||
* @param user_info.email
|
||||
* @return promise
|
||||
*/
|
||||
KM.prototype.register = function(user_info, cb) {
|
||||
var that = this;
|
||||
|
||||
needle.post(that.BASE_URI + '/api/oauth/register', user_info, {
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Register-Provider': 'pm2-register'
|
||||
}
|
||||
}, function (err, res, body) {
|
||||
if (err) return cb(err);
|
||||
if (body.email && body.email.message) return cb(body.email.message);
|
||||
if (body.username && body.username.message) return cb(body.username.message);
|
||||
|
||||
cb(null, {
|
||||
token : body.access_token.token
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
KM.prototype.defaultNode = function(cb) {
|
||||
var that = this;
|
||||
|
||||
needle.get(that.BASE_URI + '/api/node/default', function(err, res, body) {
|
||||
if (err) return cb(err);
|
||||
cb(null, url.parse(body.endpoints.web).protocol + '//' + url.parse(body.endpoints.web).hostname);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
KM.prototype.createBucket = function(default_node, bucket_name, cb) {
|
||||
var that = this;
|
||||
|
||||
needle.post(default_node + '/api/bucket/create_classic', {
|
||||
name : bucket_name
|
||||
}, {
|
||||
json : true,
|
||||
headers : {
|
||||
'Authorization' : 'Bearer ' + that.access_token
|
||||
}
|
||||
}, function(err, res, body) {
|
||||
if (err) return cb(err);
|
||||
cb(null, body);
|
||||
});
|
||||
}
|
||||
|
||||
KM.prototype.fullCreationFlow = function(user_info, cb) {
|
||||
var that = this;
|
||||
|
||||
this.register(user_info, function(err, dt) {
|
||||
if (err) return cb(err);
|
||||
that.access_token = dt.token;
|
||||
that.defaultNode(function(err, default_node) {
|
||||
if (err) return cb(err);
|
||||
that.createBucket(default_node, 'Node Monitoring', function(err, packet) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, {
|
||||
secret_id : packet.bucket.secret_id,
|
||||
public_id : packet.bucket.public_id
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = new KM;
|
||||
@ -1,8 +0,0 @@
|
||||
__ __ __ _
|
||||
/ //_/__ __ ______ ___ ___ / /______(_)_________
|
||||
/ ,< / _ \/ / / / __ `__ \/ _ \/ __/ ___/ / ___/ ___/
|
||||
/ /| / __/ /_/ / / / / / / __/ /_/ / / / /__(__ )
|
||||
/_/ |_\___/\__, /_/ /_/ /_/\___/\__/_/ /_/\___/____/
|
||||
/____/
|
||||
|
||||
Harden your Node.js application, today
|
||||
@ -3,9 +3,9 @@
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
var shelljs = require('shelljs');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var os = require('os');
|
||||
var async = require('async');
|
||||
var p = path;
|
||||
var readline = require('readline');
|
||||
@ -17,7 +17,6 @@ var Common = require('../../Common');
|
||||
var Utility = require('../../Utility.js');
|
||||
var ModularizerV1 = require('./Modularizerv1.js');
|
||||
var Modularizer = module.exports = {};
|
||||
var mkdirp = require('mkdirp');
|
||||
|
||||
var MODULE_CONF_PREFIX = 'module-db-v2';
|
||||
|
||||
@ -47,7 +46,7 @@ var KNOWN_MODULES = {
|
||||
* - Generate sample module via pm2 module:generate <module_name>
|
||||
*/
|
||||
Modularizer.install = function (CLI, moduleName, opts, cb) {
|
||||
// if user want to install module from ecosystem.json file
|
||||
// if user want to install module from ecosystem.config.js file
|
||||
// it can also be a custom json file's name
|
||||
if (!moduleName || moduleName.length === 0 || moduleName.indexOf('.json') > 0) {
|
||||
var file = moduleName || cst.APP_CONF_DEFAULT_FILE;
|
||||
@ -184,8 +183,8 @@ Modularizer.installModule = function(CLI, module_name, opts, cb) {
|
||||
var canonic_module_name = Utility.getCanonicModuleName(module_name);
|
||||
var install_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name);
|
||||
|
||||
mkdirp(install_path, function() {
|
||||
process.chdir(process.env.HOME);
|
||||
require('mkdirp')(install_path, function() {
|
||||
process.chdir(os.homedir());
|
||||
|
||||
var install_instance = spawn(cst.IS_WINDOWS ? 'npm.cmd' : 'npm', ['install', module_name, '--loglevel=error', '--prefix', install_path ], {
|
||||
stdio : 'inherit',
|
||||
@ -404,14 +403,14 @@ function uninstallModule(CLI, opts, cb) {
|
||||
|
||||
if (module_name != '.') {
|
||||
console.log(proc_path);
|
||||
shelljs.rm('-r', proc_path);
|
||||
require('shelljs').rm('-r', proc_path);
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (module_name != '.') {
|
||||
shelljs.rm('-r', proc_path);
|
||||
require('shelljs').rm('-r', proc_path);
|
||||
}
|
||||
|
||||
return cb(null, data);
|
||||
@ -435,9 +434,9 @@ var Rollback = {
|
||||
|
||||
CLI.deleteModule(canonic_module_name, function() {
|
||||
// Delete failing module
|
||||
shelljs.rm('-r', module_path);
|
||||
require('shelljs').rm('-r', module_path);
|
||||
// Restore working version
|
||||
shelljs.cp('-r', backup_path, module_path);
|
||||
require('shelljs').cp('-r', backup_path, cst.DEFAULT_MODULE_PATH);
|
||||
|
||||
var proc_path = path.join(module_path, 'node_modules', canonic_module_name);
|
||||
var package_json_path = path.join(proc_path, 'package.json');
|
||||
@ -455,7 +454,7 @@ var Rollback = {
|
||||
var tmpdir = require('os').tmpdir();
|
||||
var canonic_module_name = Utility.getCanonicModuleName(module_name);
|
||||
var module_path = path.join(cst.DEFAULT_MODULE_PATH, canonic_module_name);
|
||||
shelljs.cp('-r', module_path, tmpdir);
|
||||
require('shelljs').cp('-r', module_path, tmpdir);
|
||||
}
|
||||
}
|
||||
|
||||
@ -520,13 +519,13 @@ Modularizer.publish = function(cb) {
|
||||
package_json.name,
|
||||
package_json.version);
|
||||
|
||||
shelljs.exec('npm publish', function(code) {
|
||||
require('shelljs').exec('npm publish', function(code) {
|
||||
Common.printOut(cst.PREFIX_MSG_MOD + 'Module - %s@%s successfully published',
|
||||
package_json.name,
|
||||
package_json.version);
|
||||
|
||||
Common.printOut(cst.PREFIX_MSG_MOD + 'Pushing module on Git');
|
||||
shelljs.exec('git add . ; git commit -m "' + package_json.version + '"; git push origin master', function(code) {
|
||||
require('shelljs').exec('git add . ; git commit -m "' + package_json.version + '"; git push origin master', function(code) {
|
||||
|
||||
Common.printOut(cst.PREFIX_MSG_MOD + 'Installable with pm2 install %s', package_json.name);
|
||||
return cb(null, package_json);
|
||||
@ -549,11 +548,11 @@ Modularizer.generateSample = function(app_name, cb) {
|
||||
var cmd3 = 'cd ' + module_name + ' ; npm install';
|
||||
|
||||
Common.printOut(cst.PREFIX_MSG_MOD + 'Getting sample app');
|
||||
shelljs.exec(cmd1, function(err) {
|
||||
require('shelljs').exec(cmd1, function(err) {
|
||||
if (err) Common.printError(cst.PREFIX_MSG_MOD_ERR + err.message);
|
||||
shelljs.exec(cmd2, function(err) {
|
||||
require('shelljs').exec(cmd2, function(err) {
|
||||
console.log('');
|
||||
shelljs.exec(cmd3, function(err) {
|
||||
require('shelljs').exec(cmd3, function(err) {
|
||||
console.log('');
|
||||
Common.printOut(cst.PREFIX_MSG_MOD + 'Module sample created in folder: ', path.join(process.cwd(), module_name));
|
||||
console.log('');
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
var shelljs = require('shelljs');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var async = require('async');
|
||||
|
||||
@ -11,7 +11,6 @@ var UX = require('../CliUx');
|
||||
var chalk = require('chalk');
|
||||
var async = require('async');
|
||||
|
||||
var shelljs = require('shelljs');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var p = path;
|
||||
|
||||
@ -45,7 +45,7 @@ Monit.reset = function(msg) {
|
||||
|
||||
this.multi.charm.reset();
|
||||
|
||||
this.multi.write('\x1B[32m⌬ PM2 \x1B[39mmonitoring\x1B[96m (To go further check out https://app.keymetrics.io) \x1B[39m\n\n');
|
||||
this.multi.write('\x1B[32m⌬ PM2 \x1B[39mmonitoring\x1B[96m (To go further check out https://app.pm2.io) \x1B[39m\n\n');
|
||||
|
||||
if(msg) {
|
||||
this.multi.write(msg);
|
||||
@ -244,4 +244,4 @@ Monit.updateBars = function(proc) {
|
||||
return this;
|
||||
}
|
||||
|
||||
module.exports = Monit;
|
||||
module.exports = Monit;
|
||||
|
||||
285
lib/API/PM2/CliAuth.js
Normal file
285
lib/API/PM2/CliAuth.js
Normal file
@ -0,0 +1,285 @@
|
||||
|
||||
|
||||
'use strict'
|
||||
|
||||
const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
|
||||
|
||||
const http = require('http')
|
||||
const fs = require('fs')
|
||||
const url = require('url')
|
||||
const exec = require('child_process').exec
|
||||
const async = require('async')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const needle = require('needle');
|
||||
const chalk = require('chalk')
|
||||
const cst = require('../../../constants.js');
|
||||
|
||||
module.exports = class CustomStrategy extends AuthStrategy {
|
||||
// the client will try to call this but we handle this part ourselves
|
||||
retrieveTokens (km, cb) {
|
||||
this.authenticated = false
|
||||
this.callback = cb
|
||||
this.km = km
|
||||
this.BASE_URI = 'https://app.keymetrics.io';
|
||||
}
|
||||
|
||||
// so the cli know if we need to tell user to login/register
|
||||
isAuthenticated () {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.authenticated) return resolve(true)
|
||||
|
||||
let tokensPath = cst.PM2_IO_ACCESS_TOKEN
|
||||
fs.readFile(tokensPath, (err, tokens) => {
|
||||
if (err && err.code === 'ENOENT') return resolve(false)
|
||||
if (err) return reject(err)
|
||||
|
||||
// verify that the token is valid
|
||||
try {
|
||||
tokens = JSON.parse(tokens || '{}')
|
||||
} catch (err) {
|
||||
fs.unlinkSync(tokensPath)
|
||||
return resolve(false)
|
||||
}
|
||||
|
||||
// if the refresh tokens is here, the user could be automatically authenticated
|
||||
return resolve(typeof tokens.refresh_token === 'string')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
verifyToken (refresh) {
|
||||
return this.km.auth.retrieveToken({
|
||||
client_id: this.client_id,
|
||||
refresh_token: refresh
|
||||
})
|
||||
}
|
||||
|
||||
// called when we are sure the user asked to be logged in
|
||||
_retrieveTokens (optionalCallback) {
|
||||
const km = this.km
|
||||
const cb = this.callback
|
||||
|
||||
async.tryEach([
|
||||
// try to find the token via the environement
|
||||
(next) => {
|
||||
if (!process.env.KM_TOKEN) {
|
||||
return next(new Error('No token in env'))
|
||||
}
|
||||
this.verifyToken(process.env.KM_TOKEN)
|
||||
.then((res) => {
|
||||
return next(null, res.data)
|
||||
}).catch(next)
|
||||
},
|
||||
// try to find it in the file system
|
||||
(next) => {
|
||||
return next(new Error('nope'))
|
||||
|
||||
fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
|
||||
if (err) return next(err)
|
||||
|
||||
// verify that the token is valid
|
||||
tokens = JSON.parse(tokens || '{}')
|
||||
if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
|
||||
return next(null, tokens)
|
||||
}
|
||||
|
||||
this.verifyToken(tokens.refresh_token)
|
||||
.then((res) => {
|
||||
return next(null, res.data)
|
||||
}).catch(next)
|
||||
})
|
||||
},
|
||||
// otherwise make the whole flow
|
||||
(next) => {
|
||||
return this.loginViaCLI((data) => {
|
||||
// verify that the token is valid
|
||||
this.verifyToken(data.refresh_token)
|
||||
.then((res) => {
|
||||
return next(null, res.data)
|
||||
}).catch(next)
|
||||
})
|
||||
}
|
||||
], (err, result) => {
|
||||
// if present run the optional callback
|
||||
if (typeof optionalCallback === 'function') {
|
||||
optionalCallback(err, result)
|
||||
}
|
||||
|
||||
if (result.refresh_token) {
|
||||
this.authenticated = true
|
||||
let file = cst.PM2_IO_ACCESS_TOKEN
|
||||
fs.writeFile(file, JSON.stringify(result), () => {
|
||||
return cb(err, result)
|
||||
})
|
||||
} else {
|
||||
return cb(err, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loginViaCLI (cb) {
|
||||
var promptly = require('promptly');
|
||||
|
||||
let retry = () => {
|
||||
promptly.prompt('Username or Email: ', (err, username) => {
|
||||
if (err) return retry();
|
||||
|
||||
promptly.password('Password: ', { replace : '*' }, (err, password) => {
|
||||
if (err) return retry();
|
||||
|
||||
this._loginUser({
|
||||
username: username,
|
||||
password: password
|
||||
}, (err, data) => {
|
||||
if (err) return retry()
|
||||
cb(data)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
retry()
|
||||
}
|
||||
|
||||
_loginUser (user_info, cb) {
|
||||
const querystring = require('querystring');
|
||||
const AUTH_URI = 'https://id.keymetrics.io'
|
||||
const URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' +
|
||||
this.client_id + '&redirect_uri=https://app.keymetrics.io';
|
||||
|
||||
console.log(chalk.bold('[-] Logging to pm2.io'))
|
||||
|
||||
needle.get(AUTH_URI + URL_AUTH, (err, res) => {
|
||||
if (err) return cb(err);
|
||||
|
||||
var cookie = res.cookies;
|
||||
|
||||
needle.post(AUTH_URI + '/api/oauth/login', user_info, {
|
||||
cookies : cookie
|
||||
}, (err, resp, body) => {
|
||||
if (err) return cb(err);
|
||||
if (resp.statusCode != 200) return cb('Wrong credentials');
|
||||
|
||||
var location = resp.headers['x-redirect'];
|
||||
var redirect = AUTH_URI + location;
|
||||
|
||||
needle.get(redirect, {
|
||||
cookies : cookie
|
||||
}, (err, res) => {
|
||||
if (err) return cb(err);
|
||||
var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token;
|
||||
needle.post(AUTH_URI + '/api/oauth/token', {
|
||||
client_id : this.client_id,
|
||||
grant_type : 'refresh_token',
|
||||
refresh_token : refresh_token,
|
||||
scope : 'all'
|
||||
}, (err, res, body) => {
|
||||
if (err) return cb(err);
|
||||
console.log(chalk.bold.green('[+] Logged in!'))
|
||||
return cb(null, body);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
registerViaCLI (cb) {
|
||||
var promptly = require('promptly');
|
||||
console.log(chalk.bold('[-] Registering to pm2.io'));
|
||||
|
||||
var retry = () => {
|
||||
promptly.prompt('Username: ', {
|
||||
validator : this._validateUsername,
|
||||
retry : true
|
||||
}, (err, username) => {
|
||||
promptly.prompt('Email: ', {
|
||||
validator : this._validateEmail,
|
||||
retry : true
|
||||
},(err, email) => {
|
||||
promptly.password('Password: ', { replace : '*' }, (err, password) => {
|
||||
process.stdout.write('Creating account on pm2.io...');
|
||||
|
||||
var inter = setInterval(function() {
|
||||
process.stdout.write('.');
|
||||
}, 200);
|
||||
|
||||
this._registerUser({
|
||||
email : email,
|
||||
password : password,
|
||||
username : username
|
||||
}, (err, data) => {
|
||||
clearInterval(inter)
|
||||
if (err) {
|
||||
console.error()
|
||||
console.error(chalk.bold.red(err));
|
||||
return retry()
|
||||
}
|
||||
console.log(chalk.green.bold('\n[+] Account created!'))
|
||||
|
||||
this._loginUser({
|
||||
username: username,
|
||||
password: password
|
||||
}, (err, data) => {
|
||||
this.callback(err, data)
|
||||
return cb(err, data)
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
retry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register function
|
||||
* @param user_info.username
|
||||
* @param user_info.password
|
||||
* @param user_info.email
|
||||
*/
|
||||
_registerUser (user_info, cb) {
|
||||
needle.post(this.BASE_URI + '/api/oauth/register', user_info, {
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Register-Provider': 'pm2-register'
|
||||
}
|
||||
}, function (err, res, body) {
|
||||
if (err) return cb(err);
|
||||
if (body.email && body.email.message) return cb(body.email.message);
|
||||
if (body.username && body.username.message) return cb(body.username.message);
|
||||
if (!body.access_token) return cb(body.msg)
|
||||
|
||||
cb(null, {
|
||||
token : body.refresh_token.token
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_validateEmail (email) {
|
||||
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
if (re.test(email) == false)
|
||||
throw new Error('Not an email');
|
||||
return email;
|
||||
}
|
||||
|
||||
_validateUsername (value) {
|
||||
if (value.length < 6) {
|
||||
throw new Error('Min length of 6');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
deleteTokens (km) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// revoke the refreshToken
|
||||
km.auth.revoke()
|
||||
.then(res => {
|
||||
// remove the token from the filesystem
|
||||
let file = cst.PM2_IO_ACCESS_TOKEN
|
||||
fs.unlinkSync(file)
|
||||
return resolve(res)
|
||||
}).catch(reject)
|
||||
})
|
||||
}
|
||||
}
|
||||
245
lib/API/PM2/PM2IO.js
Normal file
245
lib/API/PM2/PM2IO.js
Normal file
@ -0,0 +1,245 @@
|
||||
'use strict'
|
||||
|
||||
var cst = require('../../../constants.js');
|
||||
var Common = require('../../Common.js');
|
||||
var KMDaemon = require('@pm2/agent/src/InteractorClient');
|
||||
|
||||
const chalk = require('chalk');
|
||||
const async = require('async');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Table = require('cli-table-redemption');
|
||||
const open = require('../../tools/open.js');
|
||||
const pkg = require('../../../package.json')
|
||||
const IOAPI = require('@pm2/js-api')
|
||||
|
||||
|
||||
// const CustomStrategy = require('./custom_auth')
|
||||
// const strategy = new CustomStrategy({
|
||||
// client_id: '7412235273'
|
||||
// })
|
||||
|
||||
const CLIAuth = require('./CliAuth')
|
||||
|
||||
const CLIAuthStrategy = new CLIAuth({
|
||||
client_id: '938758711'
|
||||
})
|
||||
|
||||
const io = new IOAPI().use(CLIAuthStrategy)
|
||||
|
||||
module.exports = function(CLI) {
|
||||
|
||||
CLI.prototype.openDashboard = function() {
|
||||
KMDaemon.getInteractInfo(this._conf, (err, data) => {
|
||||
if (err) {
|
||||
Common.printError(chalk.bold.white('Agent if offline, type `$ pm2 register` to log in'));
|
||||
return this.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
Common.printOut(chalk.bold('Opening Dashboard in Browser...'));
|
||||
open('https://app.pm2.io/#/r/' + data.public_key);
|
||||
setTimeout(_ => {
|
||||
this.exitCli();
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
|
||||
CLI.prototype.loginToKM = function() {
|
||||
var promptly = require('promptly')
|
||||
printMotd();
|
||||
|
||||
return CLIAuthStrategy._retrieveTokens((err, tokens) => {
|
||||
if (err) {
|
||||
console.error(`Oups, a error happened : ${err}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// query both the user and all bucket
|
||||
Promise.all([ io.user.retrieve(), io.bucket.retrieveAll() ])
|
||||
.then(results => {
|
||||
let user = results[0].data
|
||||
let buckets = results[1].data
|
||||
|
||||
if (buckets.length > 1) {
|
||||
var table = new Table({
|
||||
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true},
|
||||
head : ['Bucket name', 'Plan type']
|
||||
});
|
||||
|
||||
buckets.forEach(function(bucket) {
|
||||
table.push([bucket.name, bucket.credits.offer_type]);
|
||||
});
|
||||
|
||||
console.log(table.toString());
|
||||
|
||||
(function retryInsertion() {
|
||||
promptly.prompt('Type the bucket you want to link to: ', function(err, bucket_name) {
|
||||
var target_bucket = null;
|
||||
|
||||
buckets.some(function(bucket) {
|
||||
if (bucket.name == bucket_name) {
|
||||
target_bucket = bucket;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (target_bucket == null)
|
||||
return retryInsertion();
|
||||
linkOpenExit(target_bucket);
|
||||
});
|
||||
})();
|
||||
}
|
||||
else {
|
||||
var target_bucket = buckets[0];
|
||||
linkOpenExit(target_bucket)
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error(chalk.bold.red(`Oups, a error happened : ${err}`))
|
||||
return process.exit(1)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
CLI.prototype.registerToKM = function() {
|
||||
var promptly = require('promptly');
|
||||
|
||||
promptly.confirm(chalk.bold('Do you have a pm2.io account? (y/n)'), (err, answer) => {
|
||||
if (answer == true) {
|
||||
return this.loginToKM();
|
||||
}
|
||||
CLIAuthStrategy.registerViaCLI((err, data) => {
|
||||
console.log('[-] Creating Bucket...')
|
||||
|
||||
io.bucket.create({
|
||||
name: 'Node.JS Monitoring'
|
||||
}).then(res => {
|
||||
const bucket = res.data.bucket
|
||||
console.log(chalk.bold.green('[+] Bucket created!'))
|
||||
linkOpenExit(bucket)
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
CLI.prototype.logoutToKM = function () {
|
||||
CLIAuthStrategy._retrieveTokens(_ => {
|
||||
io.auth.logout()
|
||||
.then(res => {
|
||||
console.log(`- Logout successful`)
|
||||
return process.exit(0)
|
||||
}).catch(err => {
|
||||
console.error(`Oups, a error happened : ${err.message}`)
|
||||
return process.exit(1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
CLI.prototype.connectToPM2IO = function() {
|
||||
io.bucket.create({
|
||||
name: 'Node.JS Monitoring'
|
||||
}).then(res => {
|
||||
const bucket = res.data.bucket
|
||||
console.log(`Succesfully created a bucket !`)
|
||||
console.log(`To start using it, you should push data with :
|
||||
pm2 link ${bucket.secret_id} ${bucket.public_id}
|
||||
`)
|
||||
console.log(`You can also access our dedicated UI by going here :
|
||||
https://app.pm2.io/#/r/${bucket.public_id}
|
||||
`)
|
||||
|
||||
KMDaemon.launchAndInteract(cst, {
|
||||
public_key : bucket.public_id,
|
||||
secret_key : bucket.secret_id
|
||||
}, function(err, dt) {
|
||||
open(`https://app.pm2.io/#/r/${bucket.public_id}`);
|
||||
setTimeout(_ => {
|
||||
return process.exit(0)
|
||||
}, 200)
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor Selectively Processes (auto filter in interaction)
|
||||
* @param String state 'monitor' or 'unmonitor'
|
||||
* @param String target <pm_id|name|all>
|
||||
* @param Function cb callback
|
||||
*/
|
||||
CLI.prototype.monitorState = function(state, target, cb) {
|
||||
var that = this;
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
try {
|
||||
fs.statSync(this._conf.INTERACTION_CONF);
|
||||
} catch(e) {
|
||||
printMotd();
|
||||
return this.registerToKM();
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
Common.printError(cst.PREFIX_MSG_ERR + 'Please specify an <app_name|pm_id>');
|
||||
return cb ? cb(new Error('argument missing')) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
function monitor (pm_id, cb) {
|
||||
// State can be monitor or unmonitor
|
||||
that.Client.executeRemote(state, pm_id, cb);
|
||||
}
|
||||
if (target === 'all') {
|
||||
that.Client.getAllProcessId(function (err, procs) {
|
||||
if (err) {
|
||||
Common.printError(err);
|
||||
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
async.forEachLimit(procs, 1, monitor, function (err, res) {
|
||||
return typeof cb === 'function' ? cb(err, res) : that.speedList();
|
||||
});
|
||||
});
|
||||
} else if (!Number.isInteger(parseInt(target))) {
|
||||
this.Client.getProcessIdByName(target, true, function (err, procs) {
|
||||
if (err) {
|
||||
Common.printError(err);
|
||||
return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
async.forEachLimit(procs, 1, monitor, function (err, res) {
|
||||
return typeof cb === 'function' ? cb(err, res) : that.speedList();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
monitor(parseInt(target), function (err, res) {
|
||||
return typeof cb === 'function' ? cb(err, res) : that.speedList();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function linkOpenExit(target_bucket) {
|
||||
console.log('[-] Linking local PM2 to newly created bucket...')
|
||||
KMDaemon.launchAndInteract(cst, {
|
||||
public_key : target_bucket.public_id,
|
||||
secret_key : target_bucket.secret_id,
|
||||
pm2_version: pkg.version
|
||||
}, function(err, dt) {
|
||||
console.log(chalk.bold.green('[+] Local PM2 Connected!'))
|
||||
|
||||
console.log('[-] Opening Monitoring Interface in Browser...')
|
||||
|
||||
setTimeout(function() {
|
||||
open('https://app.pm2.io/#/r/' + target_bucket.public_id);
|
||||
console.log(chalk.bold.green('[+] Opened! Exiting now.'))
|
||||
setTimeout(function() {
|
||||
process.exit(cst.SUCCESS_EXIT);
|
||||
}, 100);
|
||||
}, 1000)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Private Functions
|
||||
*/
|
||||
function printMotd() {
|
||||
var dt = fs.readFileSync(path.join(__dirname, 'motd'));
|
||||
console.log(dt.toString());
|
||||
}
|
||||
};
|
||||
192
lib/API/PM2/WebAuth.js
Normal file
192
lib/API/PM2/WebAuth.js
Normal file
@ -0,0 +1,192 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const cst = require('../../../constants.js');
|
||||
|
||||
const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy')
|
||||
const http = require('http')
|
||||
const fs = require('fs')
|
||||
const url = require('url')
|
||||
const exec = require('child_process').exec
|
||||
const async = require('async')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const needle = require('needle');
|
||||
|
||||
module.exports = class CustomStrategy extends AuthStrategy {
|
||||
// the client will try to call this but we handle this part ourselves
|
||||
retrieveTokens (km, cb) {
|
||||
this.authenticated = false
|
||||
this.callback = cb
|
||||
this.km = km
|
||||
}
|
||||
|
||||
// so the cli know if we need to tell user to login/register
|
||||
isAuthenticated () {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.authenticated) return resolve(true)
|
||||
|
||||
let tokensPath = cst.PM2_IO_ACCESS_TOKEN
|
||||
fs.readFile(tokensPath, (err, tokens) => {
|
||||
if (err && err.code === 'ENOENT') return resolve(false)
|
||||
if (err) return reject(err)
|
||||
|
||||
// verify that the token is valid
|
||||
try {
|
||||
tokens = JSON.parse(tokens || '{}')
|
||||
} catch (err) {
|
||||
fs.unlinkSync(tokensPath)
|
||||
return resolve(false)
|
||||
}
|
||||
|
||||
// if the refresh tokens is here, the user could be automatically authenticated
|
||||
return resolve(typeof tokens.refresh_token === 'string')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// called when we are sure the user asked to be logged in
|
||||
_retrieveTokens (optionalCallback) {
|
||||
const km = this.km
|
||||
const cb = this.callback
|
||||
|
||||
let verifyToken = (refresh) => {
|
||||
return km.auth.retrieveToken({
|
||||
client_id: this.client_id,
|
||||
refresh_token: refresh
|
||||
})
|
||||
}
|
||||
async.tryEach([
|
||||
// try to find the token via the environement
|
||||
(next) => {
|
||||
if (!process.env.KM_TOKEN) {
|
||||
return next(new Error('No token in env'))
|
||||
}
|
||||
verifyToken(process.env.KM_TOKEN)
|
||||
.then((res) => {
|
||||
return next(null, res.data)
|
||||
}).catch(next)
|
||||
},
|
||||
// try to find it in the file system
|
||||
(next) => {
|
||||
return next(new Error('nope'))
|
||||
|
||||
fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => {
|
||||
if (err) return next(err)
|
||||
|
||||
// verify that the token is valid
|
||||
tokens = JSON.parse(tokens || '{}')
|
||||
if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) {
|
||||
return next(null, tokens)
|
||||
}
|
||||
|
||||
verifyToken(tokens.refresh_token)
|
||||
.then((res) => {
|
||||
return next(null, res.data)
|
||||
}).catch(next)
|
||||
})
|
||||
},
|
||||
// otherwise make the whole flow
|
||||
(next) => {
|
||||
return this.loginViaWeb((data) => {
|
||||
// verify that the token is valid
|
||||
verifyToken(data.access_token)
|
||||
.then((res) => {
|
||||
return next(null, res.data)
|
||||
}).catch(next)
|
||||
})
|
||||
}
|
||||
], (err, result) => {
|
||||
// if present run the optional callback
|
||||
if (typeof optionalCallback === 'function') {
|
||||
optionalCallback(err, result)
|
||||
}
|
||||
|
||||
if (result.refresh_token) {
|
||||
this.authenticated = true
|
||||
let file = cst.PM2_IO_ACCESS_TOKEN
|
||||
fs.writeFile(file, JSON.stringify(result), () => {
|
||||
return cb(err, result)
|
||||
})
|
||||
} else {
|
||||
return cb(err, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loginViaWeb (cb) {
|
||||
let shutdown = false
|
||||
let server = http.createServer((req, res) => {
|
||||
// only handle one request
|
||||
if (shutdown === true) return res.end()
|
||||
shutdown = true
|
||||
|
||||
let query = url.parse(req.url, true).query
|
||||
|
||||
res.write(`
|
||||
<head>
|
||||
<script>
|
||||
setTimeout(function () {
|
||||
window.location = 'https://pm2.io/doc/en/plus/quick-start/'
|
||||
}, 5000)
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h2 style="text-align: center">
|
||||
You can go back to your terminal now :)
|
||||
</h2>
|
||||
</body>`)
|
||||
res.end()
|
||||
server.close()
|
||||
return cb(query)
|
||||
})
|
||||
server.listen(43532, () => {
|
||||
this.open(`${this.oauth_endpoint}${this.oauth_query}`)
|
||||
})
|
||||
}
|
||||
|
||||
deleteTokens (km) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// revoke the refreshToken
|
||||
km.auth.revoke()
|
||||
.then(res => {
|
||||
// remove the token from the filesystem
|
||||
let file = cst.PM2_IO_ACCESS_TOKEN
|
||||
fs.unlinkSync(file)
|
||||
return resolve(res)
|
||||
}).catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
open (target, appName, callback) {
|
||||
let opener
|
||||
const escape = function (s) {
|
||||
return s.replace(/"/g, '\\"')
|
||||
}
|
||||
|
||||
if (typeof (appName) === 'function') {
|
||||
callback = appName
|
||||
appName = null
|
||||
}
|
||||
|
||||
switch (process.platform) {
|
||||
case 'darwin': {
|
||||
opener = appName ? `open -a "${escape(appName)}"` : `open`
|
||||
break
|
||||
}
|
||||
case 'win32': {
|
||||
opener = appName ? `start "" ${escape(appName)}"` : `start ""`
|
||||
break
|
||||
}
|
||||
default: {
|
||||
opener = appName ? escape(appName) : `xdg-open`
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.SUDO_USER) {
|
||||
opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener
|
||||
}
|
||||
return exec(`${opener} "${escape(target)}"`, callback)
|
||||
}
|
||||
}
|
||||
13
lib/API/PM2/motd
Normal file
13
lib/API/PM2/motd
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
88888888ba 88b d88 ad888888b, 88 ,ad8888ba,
|
||||
88 "8b 888b d888 d8" "88 88 d8"' `"8b
|
||||
88 ,8P 88`8b d8'88 a8P 88 d8' `8b
|
||||
88aaaaaa8P' 88 `8b d8' 88 ,d8P" 88 88 88
|
||||
88""""""' 88 `8b d8' 88 a8P" 88 88 88
|
||||
88 88 `8b d8' 88 a8P' 88 Y8, ,8P
|
||||
88 88 `888' 88 d8" 888 88 Y8a. .a8P
|
||||
88 88 `8' 88 88888888888 888 88 `"Y8888Y"'
|
||||
|
||||
https://pm2.io/
|
||||
|
||||
Add Control and Monitoring to Your Node.js Apps
|
||||
@ -8,7 +8,7 @@ var http = require('http');
|
||||
var url = require('url');
|
||||
var path = require('path');
|
||||
var debug = require('debug')('pm2:serve');
|
||||
var probe = require('pmx').probe();
|
||||
var probe = require('@pm2/io').probe();
|
||||
|
||||
/**
|
||||
* list of supported content types.
|
||||
|
||||
@ -12,7 +12,6 @@ var exec = require('child_process').exec;
|
||||
var Common = require('../Common.js');
|
||||
var cst = require('../../constants.js');
|
||||
var spawn = require('child_process').spawn;
|
||||
var shelljs = require('shelljs');
|
||||
|
||||
module.exports = function(CLI) {
|
||||
/**
|
||||
@ -42,12 +41,13 @@ module.exports = function(CLI) {
|
||||
'chkconfig' : 'systemv',
|
||||
'rc-update' : 'openrc',
|
||||
'launchctl' : 'launchd',
|
||||
'sysrc' : 'rcd'
|
||||
'sysrc' : 'rcd',
|
||||
'rcctl' : 'rcd-openbsd',
|
||||
};
|
||||
var init_systems = Object.keys(hash_map);
|
||||
|
||||
for (var i = 0; i < init_systems.length; i++) {
|
||||
if (shelljs.which(init_systems[i]) != null) {
|
||||
if (require('shelljs').which(init_systems[i]) != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -148,9 +148,19 @@ module.exports = function(CLI) {
|
||||
'sysrc -x ' + service_name + '_enable',
|
||||
'rm /usr/local/etc/rc.d/' + service_name
|
||||
];
|
||||
break;
|
||||
case 'rcd-openbsd':
|
||||
service_name = (opts.serviceName || 'pm2_' + user);
|
||||
var destination = path.join('/etc/rc.d', service_name);
|
||||
commands = [
|
||||
'rcctl stop ' + service_name,
|
||||
'rcctl disable ' + service_name,
|
||||
'rm ' + destination
|
||||
];
|
||||
break;
|
||||
};
|
||||
|
||||
shelljs.exec(commands.join('&& '), function(code, stdout, stderr) {
|
||||
require('shelljs').exec(commands.join('&& '), function(code, stdout, stderr) {
|
||||
Common.printOut(stdout);
|
||||
Common.printOut(stderr);
|
||||
if (code == 0) {
|
||||
@ -222,21 +232,9 @@ module.exports = function(CLI) {
|
||||
else
|
||||
template = getTemplate('systemd');
|
||||
destination = '/etc/systemd/system/' + service_name + '.service';
|
||||
|
||||
commands = [
|
||||
'systemctl enable ' + service_name
|
||||
]
|
||||
|
||||
try {
|
||||
fs.readFileSync(cst.PM2_PID_FILE_PATH).toString()
|
||||
} catch(e) {
|
||||
commands = [
|
||||
'systemctl enable ' + service_name,
|
||||
'systemctl start ' + service_name,
|
||||
'systemctl daemon-reload',
|
||||
'systemctl status ' + service_name
|
||||
]
|
||||
}
|
||||
];
|
||||
break;
|
||||
case 'ubuntu14':
|
||||
case 'ubuntu12':
|
||||
@ -283,6 +281,17 @@ module.exports = function(CLI) {
|
||||
'sysrc ' + service_name + '_enable=YES'
|
||||
];
|
||||
break;
|
||||
case 'openbsd':
|
||||
case 'rcd-openbsd':
|
||||
template = getTemplate('rcd-openbsd');
|
||||
service_name = (opts.serviceName || 'pm2_' + user);
|
||||
destination = path.join('/etc/rc.d/', service_name);
|
||||
commands = [
|
||||
'chmod 755 ' + destination,
|
||||
'rcctl enable ' + service_name,
|
||||
'rcctl start ' + service_name
|
||||
];
|
||||
break;
|
||||
case 'openrc':
|
||||
template = getTemplate('openrc');
|
||||
service_name = openrc_service_name;
|
||||
@ -326,7 +335,7 @@ module.exports = function(CLI) {
|
||||
|
||||
async.forEachLimit(commands, 1, function(command, next) {
|
||||
Common.printOut(cst.PREFIX_MSG + '[-] Executing: %s...', chalk.bold(command));
|
||||
shelljs.exec(command, function(code, stdout, stderr) {
|
||||
require('shelljs').exec(command, function(code, stdout, stderr) {
|
||||
if (code === 0) {
|
||||
Common.printOut(cst.PREFIX_MSG + chalk.bold('[v] Command successfully executed.'));
|
||||
return next();
|
||||
@ -380,13 +389,32 @@ module.exports = function(CLI) {
|
||||
* @return
|
||||
*/
|
||||
function fin(err) {
|
||||
|
||||
// try to fix issues with empty dump file
|
||||
// like #3485
|
||||
if (env_arr.length === 0) {
|
||||
|
||||
// fix : if no dump file, no process, only module and after pm2 update
|
||||
if (!fs.existsSync(cst.DUMP_FILE_PATH)) {
|
||||
that.clearDump(function(){});
|
||||
}
|
||||
|
||||
// if no process in list don't modify dump file
|
||||
// process list should not be empty
|
||||
if(cb) {
|
||||
return cb(null, {success: true});
|
||||
} else {
|
||||
Common.printOut(cst.PREFIX_MSG + 'Nothing to save !!!');
|
||||
Common.printOut(cst.PREFIX_MSG + 'In this case we keep old dump file. To clear dump file you can delete it manually !');
|
||||
that.exitCli(cst.SUCCESS_EXIT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Back up dump file
|
||||
try {
|
||||
if (fs.existsSync(cst.DUMP_FILE_PATH)) {
|
||||
if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
|
||||
fs.unlinkSync(cst.DUMP_BACKUP_FILE_PATH);
|
||||
}
|
||||
fs.renameSync(cst.DUMP_FILE_PATH, cst.DUMP_BACKUP_FILE_PATH);
|
||||
fs.writeFileSync(cst.DUMP_BACKUP_FILE_PATH, fs.readFileSync(cst.DUMP_FILE_PATH));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack || e);
|
||||
@ -399,8 +427,13 @@ module.exports = function(CLI) {
|
||||
} catch (e) {
|
||||
console.error(e.stack || e);
|
||||
try {
|
||||
fs.unlinkSync(cst.DUMP_FILE_PATH);
|
||||
// try to backup file
|
||||
if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
|
||||
fs.writeFileSync(cst.DUMP_FILE_PATH, fs.readFileSync(cst.DUMP_BACKUP_FILE_PATH));
|
||||
}
|
||||
} catch (e) {
|
||||
// don't keep broken file
|
||||
fs.unlinkSync(cst.DUMP_FILE_PATH);
|
||||
console.error(e.stack || e);
|
||||
}
|
||||
Common.printOut(cst.PREFIX_MSG_ERR + 'Failed to save dump file in %s', cst.DUMP_FILE_PATH);
|
||||
@ -424,6 +457,21 @@ module.exports = function(CLI) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove DUMP_FILE_PATH file and DUMP_BACKUP_FILE_PATH file
|
||||
* @method dump
|
||||
* @param {} cb
|
||||
* @return
|
||||
*/
|
||||
CLI.prototype.clearDump = function(cb) {
|
||||
fs.writeFileSync(cst.DUMP_FILE_PATH, JSON.stringify([]));
|
||||
|
||||
if(cb && typeof cb === 'function') return cb();
|
||||
|
||||
Common.printOut(cst.PREFIX_MSG + 'Successfully created %s', cst.DUMP_FILE_PATH);
|
||||
return this.exitCli(cst.SUCCESS_EXIT);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resurrect processes
|
||||
* @method resurrect
|
||||
|
||||
@ -3,7 +3,6 @@ var cst = require('../../constants.js');
|
||||
var Common = require('../Common.js');
|
||||
var fs = require('fs');
|
||||
var async = require('async');
|
||||
var vizion = require('vizion');
|
||||
var child = require('child_process');
|
||||
|
||||
var printError = Common.printError;
|
||||
@ -21,11 +20,11 @@ module.exports = function(CLI) {
|
||||
|
||||
printOut(cst.PREFIX_MSG + 'Updating repository for process name %s', process_name);
|
||||
|
||||
that.Client.getProcessByName(process_name, function(err, processes) {
|
||||
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
|
||||
|
||||
if (processes.length === 0) {
|
||||
printError('No processes with this name: %s', process_name);
|
||||
return cb ? cb({msg:'Process not found: '+process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
if (err || processes.length === 0) {
|
||||
printError('No processes with this name or id : %s', process_name);
|
||||
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
var proc = processes[0];
|
||||
@ -33,7 +32,7 @@ module.exports = function(CLI) {
|
||||
printOut(cst.PREFIX_MSG + 'No versioning system found for process %s', process_name);
|
||||
return cb ? cb({success:false, msg: 'No versioning system found for process'}) : that.exitCli(cst.SUCCESS_EXIT);
|
||||
}
|
||||
vizion.update({
|
||||
require('vizion').update({
|
||||
folder: proc.pm2_env.versioning.repo_path
|
||||
}, function(err, meta) {
|
||||
if (err !== null) {
|
||||
@ -82,19 +81,19 @@ module.exports = function(CLI) {
|
||||
|
||||
printOut(cst.PREFIX_MSG + 'Updating repository for process name %s', process_name);
|
||||
|
||||
that.Client.getProcessByName(process_name, function(err, processes) {
|
||||
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
|
||||
|
||||
if (processes.length === 0) {
|
||||
printError('No processes with this name: %s', process_name);
|
||||
return cb ? cb({msg:'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
if (err || processes.length === 0) {
|
||||
printError('No processes with this name or id : %s', process_name);
|
||||
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
var proc = processes[0];
|
||||
if (proc.pm2_env.versioning) {
|
||||
vizion.isUpToDate({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
|
||||
require('vizion').isUpToDate({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
|
||||
if (err !== null)
|
||||
return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT);
|
||||
vizion.revertTo(
|
||||
require('vizion').revertTo(
|
||||
{revision: commit_id,
|
||||
folder: proc.pm2_env.versioning.repo_path},
|
||||
function(err2, meta2) {
|
||||
@ -138,20 +137,22 @@ module.exports = function(CLI) {
|
||||
var that = this;
|
||||
printOut(cst.PREFIX_MSG + 'Downgrading to previous commit repository for process name %s', process_name);
|
||||
|
||||
that.Client.getProcessByName(process_name, function(err, processes) {
|
||||
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
|
||||
|
||||
if (processes.length === 0) {
|
||||
printError('No processes with this name: %s', process_name);
|
||||
return cb ? cb({msg:'Process not found: '+process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
if (err || processes.length === 0) {
|
||||
printError('No processes with this name or id : %s', process_name);
|
||||
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
var proc = processes[0];
|
||||
// in case user searched by id/pid
|
||||
process_name = proc.name;
|
||||
|
||||
if (proc.pm2_env.versioning === undefined ||
|
||||
proc.pm2_env.versioning === null)
|
||||
return cb({msg : 'Versioning unknown'});
|
||||
|
||||
vizion.prev({
|
||||
require('vizion').prev({
|
||||
folder: proc.pm2_env.versioning.repo_path
|
||||
}, function(err, meta) {
|
||||
if (err)
|
||||
@ -165,7 +166,7 @@ module.exports = function(CLI) {
|
||||
getPostUpdateCmds(proc.pm2_env.versioning.repo_path, process_name, function (command_list) {
|
||||
execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) {
|
||||
if (err !== null) {
|
||||
vizion.next({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
|
||||
require('vizion').next({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
|
||||
printError(err);
|
||||
return cb ? cb({msg: meta.output + err}) : that.exitCli(cst.ERROR_EXIT);
|
||||
});
|
||||
@ -194,16 +195,18 @@ module.exports = function(CLI) {
|
||||
var that = this;
|
||||
printOut(cst.PREFIX_MSG + 'Updating to next commit repository for process name %s', process_name);
|
||||
|
||||
that.Client.getProcessByName(process_name, function(err, processes) {
|
||||
that.Client.getProcessByNameOrId(process_name, function (err, processes) {
|
||||
|
||||
if (processes.length === 0) {
|
||||
printError('No processes with this name: %s', process_name);
|
||||
return cb ? cb({msg:'Process not found: '+process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
if (err || processes.length === 0) {
|
||||
printError('No processes with this name or id: %s', process_name);
|
||||
return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT);
|
||||
}
|
||||
|
||||
var proc = processes[0];
|
||||
// in case user searched by id/pid
|
||||
process_name = proc.name;
|
||||
if (proc.pm2_env.versioning) {
|
||||
vizion.next({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
|
||||
require('vizion').next({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) {
|
||||
if (err !== null)
|
||||
return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT);
|
||||
if (meta.success === true) {
|
||||
@ -211,7 +214,7 @@ module.exports = function(CLI) {
|
||||
execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) {
|
||||
if (err !== null)
|
||||
{
|
||||
vizion.prev({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
|
||||
require('vizion').prev({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) {
|
||||
printError(err);
|
||||
return cb ? cb({msg:meta.output + err}) : that.exitCli(cst.ERROR_EXIT);
|
||||
});
|
||||
@ -366,16 +369,6 @@ module.exports = function(CLI) {
|
||||
this._pull({process_name: process_name, action: 'reload'}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* CLI method for updating a repository
|
||||
* @method pullAndGracefulReload
|
||||
* @param {string} process_name name of processes to pull
|
||||
* @return
|
||||
*/
|
||||
CLI.prototype.pullAndGracefulReload = function (process_name, cb) {
|
||||
this._pull({process_name: process_name, action: 'gracefulReload'}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* CLI method for updating a repository to a specific commit id
|
||||
* @method pullCommitId
|
||||
|
||||
@ -2,23 +2,87 @@
|
||||
"script": {
|
||||
"type": "string",
|
||||
"require": true,
|
||||
"alias" : "exec"
|
||||
"alias" : "exec",
|
||||
"docDescription": "Path of the script to launch, required field"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"docDefault": "Script filename without the extension (app for app.js)",
|
||||
"docDescription": "Process name in the process list"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"docDefault": "CWD of the current environment (from your shell)",
|
||||
"docDescription": "Current working directory to start the process with"
|
||||
},
|
||||
"args": {
|
||||
"type": [
|
||||
"array",
|
||||
"string"
|
||||
]
|
||||
],
|
||||
"docDescription": "Arguments to pass to the script"
|
||||
},
|
||||
"exec_interpreter": {
|
||||
"type": "string",
|
||||
"alias": "interpreter",
|
||||
"docDefault": "node",
|
||||
"docDescription": "Interpreter absolute path"
|
||||
},
|
||||
"node_args": {
|
||||
"type": [
|
||||
"array",
|
||||
"string"
|
||||
],
|
||||
"alias": ["interpreterArgs", "interpreter_args"]
|
||||
"alias": ["interpreterArgs", "interpreter_args"],
|
||||
"docDescription": "Arguments to pass to the interpreter"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"out_file": {
|
||||
"type": "string",
|
||||
"alias": ["out", "output", "out_log"],
|
||||
"docDefault": "~/.pm2/logs/<app_name>-out.log",
|
||||
"docDescription": "File path for stdout (each line is appended to this file)"
|
||||
},
|
||||
"error_file": {
|
||||
"type": "string",
|
||||
"alias": ["error", "err", "err_file", "err_log"],
|
||||
"docDefault": "~/.pm2/logs/<app_name>-error.err",
|
||||
"docDescription": "File path for stderr (each line is appended to this file)"
|
||||
},
|
||||
"log_file": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"string"
|
||||
],
|
||||
"alias": "log",
|
||||
"docDefault": "/dev/null",
|
||||
"docDescription": "File path for combined stdout and stderr (each line is appended to this file)"
|
||||
},
|
||||
"disable_logs": {
|
||||
"type": "boolean",
|
||||
"docDefault": false,
|
||||
"docDescription": "Disable all logs storage"
|
||||
},
|
||||
"log_type": {
|
||||
"type": "string",
|
||||
"docDescription": "Define a specific log output type, possible value: json"
|
||||
},
|
||||
"log_date_format": {
|
||||
"type": "string",
|
||||
"docDescription": "Format for log timestamps in moment.js format (eg YYYY-MM-DD HH:mm Z)"
|
||||
},
|
||||
"env": {
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"docDescription": "Specify environment variables to be injected"
|
||||
},
|
||||
"^env_\\S*$": {
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
],
|
||||
"docDescription": "Specify environment variables to be injected when using --env <env_name>"
|
||||
},
|
||||
"max_memory_restart": {
|
||||
"type": [
|
||||
@ -27,124 +91,91 @@
|
||||
],
|
||||
"regex": "^\\d+(G|M|K)?$",
|
||||
"ext_type": "sbyte",
|
||||
"desc": "it should be a NUMBER - byte, \"[NUMBER]G\"(Gigabyte), \"[NUMBER]M\"(Megabyte) or \"[NUMBER]K\"(Kilobyte)"
|
||||
},
|
||||
"uid" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"gid" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"restart_delay": {
|
||||
"type" : "number"
|
||||
},
|
||||
"source_map_support" : {
|
||||
"type": "boolean"
|
||||
},
|
||||
"wait_ready" : {
|
||||
"type": "boolean"
|
||||
},
|
||||
"disable_source_map_support" : {
|
||||
"type": "boolean"
|
||||
},
|
||||
"instances": {
|
||||
"type": "number"
|
||||
},
|
||||
"kill_timeout": {
|
||||
"type": "number"
|
||||
},
|
||||
"listen_timeout": {
|
||||
"type": "number"
|
||||
},
|
||||
"port": {
|
||||
"type": "number"
|
||||
},
|
||||
"log_file": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"string"
|
||||
],
|
||||
"alias": "log"
|
||||
},
|
||||
"error_file": {
|
||||
"type": "string",
|
||||
"alias": ["error", "err", "err_file", "err_log"]
|
||||
},
|
||||
"log_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"out_file": {
|
||||
"type": "string",
|
||||
"alias": ["output", "out", "out_log"]
|
||||
"desc": "it should be a NUMBER - byte, \"[NUMBER]G\"(Gigabyte), \"[NUMBER]M\"(Megabyte) or \"[NUMBER]K\"(Kilobyte)",
|
||||
"docDescription": "Restart the app if an amount of memory is exceeded (format: /[0-9](K|M|G)?/ K for KB, 'M' for MB, 'G' for GB, default to B)"
|
||||
},
|
||||
"pid_file": {
|
||||
"type": "string",
|
||||
"alias": "pid"
|
||||
"alias": "pid",
|
||||
"docDefault": "~/.pm2/pids/app_name-id.pid",
|
||||
"docDescription": "File path where the pid of the started process is written by pm2"
|
||||
},
|
||||
"restart_delay": {
|
||||
"type" : "number",
|
||||
"docDefault": 0,
|
||||
"docDescription": "Time in ms to wait before restarting a crashing app"
|
||||
},
|
||||
"source_map_support": {
|
||||
"type": "boolean",
|
||||
"docDefault": true,
|
||||
"docDescription": "Enable or disable the source map support"
|
||||
},
|
||||
"disable_source_map_support": {
|
||||
"type": "boolean",
|
||||
"docDefault": false,
|
||||
"docDescription": "Enable or disable the source map support"
|
||||
},
|
||||
"wait_ready": {
|
||||
"type": "boolean",
|
||||
"docDefault": false,
|
||||
"docDescription": "Make the process wait for a process.send('ready')"
|
||||
},
|
||||
"instances": {
|
||||
"type": "number",
|
||||
"docDefault": 1,
|
||||
"docDescription": "Number of instances to be started in cluster mode"
|
||||
},
|
||||
"kill_timeout": {
|
||||
"type": "number",
|
||||
"docDefault": 1600,
|
||||
"docDescription": "Time in ms before sending the final SIGKILL signal after SIGINT"
|
||||
},
|
||||
"listen_timeout": {
|
||||
"type": "number",
|
||||
"docDescription": "Time in ms before forcing a reload if app is still not listening/has still note sent ready"
|
||||
},
|
||||
"cron_restart": {
|
||||
"type": "string",
|
||||
"alias": "cron"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
"alias": "cron",
|
||||
"docDescription": "A cron pattern to restart your app"
|
||||
},
|
||||
"merge_logs": {
|
||||
"type": "boolean",
|
||||
"alias" : "combine_logs"
|
||||
"alias" : "combine_logs",
|
||||
"docDefault": false,
|
||||
"docDescription": "In cluster mode, merge each type of logs into a single file (instead of having one for each cluster)"
|
||||
},
|
||||
"vizion" : {
|
||||
"vizion": {
|
||||
"type": "boolean",
|
||||
"default" : true
|
||||
"default" : true,
|
||||
"docDefault" : "True",
|
||||
"docDescription": "Enable or disable the versioning metadatas (vizion library)"
|
||||
},
|
||||
"pmx" : {
|
||||
"autorestart": {
|
||||
"type": "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"automation" : {
|
||||
"type": "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"autorestart" : {
|
||||
"type": "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"treekill" : {
|
||||
"type": "boolean",
|
||||
"default" : true
|
||||
"default": true,
|
||||
"docDefault": "True",
|
||||
"docDescription": "Enable or disable auto restart after process failure"
|
||||
},
|
||||
"watch": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"array",
|
||||
"string"
|
||||
]
|
||||
],
|
||||
"docDefault": false,
|
||||
"docDescription": "Enable or disable the watch mode"
|
||||
},
|
||||
"ignore_watch": {
|
||||
"type": [
|
||||
"array",
|
||||
"string"
|
||||
]
|
||||
],
|
||||
"docDescription": "List of paths to ignore (regex)"
|
||||
},
|
||||
"watch_options": {
|
||||
"type": "object"
|
||||
},
|
||||
"env": {
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"^env_\\S*$": {
|
||||
"type": [
|
||||
"object",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"disable_logs" : {
|
||||
"type": "boolean"
|
||||
},
|
||||
"log_date_format": {
|
||||
"type": "string"
|
||||
"type": "object",
|
||||
"docDescription": "Object that will be used as an options with chokidar (refer to chokidar documentation)"
|
||||
},
|
||||
"min_uptime": {
|
||||
"type": [
|
||||
@ -154,43 +185,51 @@
|
||||
"regex": "^\\d+(h|m|s)?$",
|
||||
"desc": "it should be a NUMBER - milliseconds, \"[NUMBER]h\"(hours), \"[NUMBER]m\"(minutes) or \"[NUMBER]s\"(seconds)",
|
||||
"min": 100,
|
||||
"ext_type": "stime"
|
||||
"ext_type": "stime",
|
||||
"docDefault": 1000,
|
||||
"docDescription": "Minimum uptime of the app to be considered started (format is /[0-9]+(h|m|s)?/, for hours, minutes, seconds, docDefault to ms)"
|
||||
},
|
||||
"max_restarts": {
|
||||
"type": "number",
|
||||
"min": 0
|
||||
"min": 0,
|
||||
"docDefault": 16,
|
||||
"docDescription": "Number of times a script is restarted when it exits in less than min_uptime"
|
||||
},
|
||||
"exec_mode": {
|
||||
"type": "string",
|
||||
"regex": "^(cluster|fork)(_mode)?$",
|
||||
"alias": "executeCommand",
|
||||
"desc": "it should be \"cluster\"(\"cluster_mode\") or \"fork\"(\"fork_mode\") only"
|
||||
},
|
||||
"exec_interpreter": {
|
||||
"type": "string",
|
||||
"alias": "interpreter"
|
||||
},
|
||||
"write": {
|
||||
"type": "boolean"
|
||||
"desc": "it should be \"cluster\"(\"cluster_mode\") or \"fork\"(\"fork_mode\") only",
|
||||
"docDefault": "fork",
|
||||
"docDescription": "Set the execution mode, possible values: fork|cluster"
|
||||
},
|
||||
"force": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"docDefault": false,
|
||||
"docDescription": "Start a script even if it is already running (only the script path is considered)"
|
||||
},
|
||||
"append_env_to_name": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"docDefault": false,
|
||||
"docDescription": "Append the environment name to the app name"
|
||||
},
|
||||
"post_update": {
|
||||
"type": "array"
|
||||
},
|
||||
"disable_trace": {
|
||||
"type": [
|
||||
"boolean"
|
||||
]
|
||||
"type": "array",
|
||||
"docDescription": "List of commands executed after a pull/upgrade operation performed from Keymetrics dashboard"
|
||||
},
|
||||
"trace": {
|
||||
"type": [
|
||||
"boolean"
|
||||
]
|
||||
],
|
||||
"docDefault": false,
|
||||
"docDescription": "Enable or disable the transaction tracing"
|
||||
},
|
||||
"disable_trace": {
|
||||
"type": [
|
||||
"boolean"
|
||||
],
|
||||
"docDefault": true,
|
||||
"docDescription": "Enable or disable the transaction tracing"
|
||||
},
|
||||
"v8": {
|
||||
"type": [
|
||||
@ -208,14 +247,66 @@
|
||||
]
|
||||
},
|
||||
"increment_var": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"docDescription": "Specify the name of an environment variable to inject which increments for each cluster"
|
||||
},
|
||||
"instance_var": {
|
||||
"type": "string",
|
||||
"default" : "NODE_APP_INSTANCE"
|
||||
},
|
||||
"default": "NODE_APP_INSTANCE",
|
||||
"docDefault": "NODE_APP_INSTANCE",
|
||||
"docDescription": "Rename the NODE_APP_INSTANCE environment variable"
|
||||
},
|
||||
"pmx": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"docDefault": "True",
|
||||
"docDescription": "Enable or disable pmx wrapping"
|
||||
},
|
||||
"automation": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"docDefault": "True",
|
||||
"docDescription": "Enable or disable pmx wrapping"
|
||||
},
|
||||
"treekill": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"docDefault": "True",
|
||||
"docDescription": "Only kill the main process, not detached children"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"docDescription": "Shortcut to inject a PORT environment variable"
|
||||
},
|
||||
"username" : {
|
||||
"type": "string",
|
||||
"docDescription": "Current user that started the process"
|
||||
},
|
||||
"uid": {
|
||||
"type" : "string",
|
||||
"docDefault": "Current user uid",
|
||||
"docDescription": "Set user id"
|
||||
},
|
||||
"gid": {
|
||||
"type" : "string",
|
||||
"docDefault": "Current user gid",
|
||||
"docDescription": "Set group id"
|
||||
},
|
||||
"windowsHide": {
|
||||
"type": "boolean",
|
||||
"default" : true
|
||||
"docDefault": "True",
|
||||
"docDescription": "Enable or disable the Windows popup when starting an app",
|
||||
"default": true
|
||||
},
|
||||
"kill_retry_time": {
|
||||
"type": "number",
|
||||
"default" : 100
|
||||
},
|
||||
"write": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"io": {
|
||||
"type": "object",
|
||||
"docDescription": "Specify apm values and configuration"
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,15 +6,14 @@
|
||||
|
||||
var debug = require('debug')('pm2:client');
|
||||
var Common = require('./Common.js');
|
||||
var KMDaemon = require('./Interactor/InteractorDaemonizer.js');
|
||||
var KMDaemon = require('@pm2/agent/src/InteractorClient');
|
||||
var rpc = require('pm2-axon-rpc');
|
||||
var async = require('async');
|
||||
var axon = require('pm2-axon');
|
||||
var util = require('util');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var shelljs = require('shelljs');
|
||||
var pkg = require('../package.json')
|
||||
|
||||
function noop() {}
|
||||
|
||||
@ -79,7 +78,8 @@ Client.prototype.start = function(cb) {
|
||||
KMDaemon.launchAndInteract(that.conf, {
|
||||
machine_name : that.machine_name,
|
||||
public_key : that.public_key,
|
||||
secret_key : that.secret_key
|
||||
secret_key : that.secret_key,
|
||||
pm2_version : pkg.version
|
||||
}, function(err, data, interactor_proc) {
|
||||
that.interactor_process = interactor_proc;
|
||||
});
|
||||
@ -129,7 +129,7 @@ Client.prototype.start = function(cb) {
|
||||
Client.prototype.initFileStructure = function (opts) {
|
||||
if (!fs.existsSync(opts.DEFAULT_LOG_PATH)) {
|
||||
try {
|
||||
mkdirp.sync(opts.DEFAULT_LOG_PATH);
|
||||
require('mkdirp').sync(opts.DEFAULT_LOG_PATH);
|
||||
} catch (e) {
|
||||
console.error(e.stack || e);
|
||||
}
|
||||
@ -137,7 +137,7 @@ Client.prototype.initFileStructure = function (opts) {
|
||||
|
||||
if (!fs.existsSync(opts.DEFAULT_PID_PATH)) {
|
||||
try {
|
||||
mkdirp.sync(opts.DEFAULT_PID_PATH);
|
||||
require('mkdirp').sync(opts.DEFAULT_PID_PATH);
|
||||
} catch (e) {
|
||||
console.error(e.stack || e);
|
||||
}
|
||||
@ -153,7 +153,7 @@ Client.prototype.initFileStructure = function (opts) {
|
||||
|
||||
if (!fs.existsSync(opts.DEFAULT_MODULE_PATH)) {
|
||||
try {
|
||||
mkdirp.sync(opts.DEFAULT_MODULE_PATH);
|
||||
require('mkdirp').sync(opts.DEFAULT_MODULE_PATH);
|
||||
} catch (e) {
|
||||
console.error(e.stack || e);
|
||||
}
|
||||
@ -241,7 +241,7 @@ Client.prototype.launchDaemon = function(opts, cb) {
|
||||
|
||||
var interpreter = 'node';
|
||||
|
||||
if (shelljs.which('node') == null)
|
||||
if (require('shelljs').which('node') == null)
|
||||
interpreter = process.execPath;
|
||||
|
||||
var child = require('child_process').spawn(interpreter, node_args, {
|
||||
@ -278,7 +278,8 @@ Client.prototype.launchDaemon = function(opts, cb) {
|
||||
KMDaemon.launchAndInteract(that.conf, {
|
||||
machine_name : that.machine_name,
|
||||
public_key : that.public_key,
|
||||
secret_key : that.secret_key
|
||||
secret_key : that.secret_key,
|
||||
pm2_version : pkg.version
|
||||
}, function(err, data, interactor_proc) {
|
||||
that.interactor_process = interactor_proc;
|
||||
return cb(null, child);
|
||||
@ -717,3 +718,25 @@ Client.prototype.getProcessByName = function(name, cb) {
|
||||
return cb(null, found_proc);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.getProcessByNameOrId = function (nameOrId, cb) {
|
||||
var foundProc = [];
|
||||
|
||||
this.executeRemote('getMonitorData', {}, function (err, list) {
|
||||
if (err) {
|
||||
Common.printError('Error retrieving process list: ' + err);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
list.forEach(function (proc) {
|
||||
if (proc.pm2_env.name === nameOrId ||
|
||||
proc.pm2_env.pm_exec_path === path.resolve(nameOrId) ||
|
||||
proc.pid === parseInt(nameOrId) ||
|
||||
proc.pm2_env.pm_id === parseInt(nameOrId)) {
|
||||
foundProc.push(proc);
|
||||
}
|
||||
});
|
||||
|
||||
return cb(null, foundProc);
|
||||
});
|
||||
};
|
||||
|
||||
@ -12,9 +12,7 @@ var fs = require('fs');
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
var util = require('util');
|
||||
var mkdirp = require('mkdirp');
|
||||
var async = require('async');
|
||||
var shelljs = require('shelljs');
|
||||
var chalk = require('chalk');
|
||||
var fclone = require('fclone');
|
||||
var semver = require('semver');
|
||||
@ -23,7 +21,6 @@ var isBinary = require('./tools/isbinaryfile.js');
|
||||
var cst = require('../constants.js');
|
||||
var extItps = require('./API/interpreter.json');
|
||||
var Config = require('./tools/Config');
|
||||
var KMDaemon = require('./Interactor/InteractorDaemonizer.js');
|
||||
|
||||
var Common = module.exports;
|
||||
|
||||
@ -151,7 +148,7 @@ Common.prepareAppConf = function(opts, app) {
|
||||
if (!fs.existsSync(app.pm_exec_path)) {
|
||||
var ckd;
|
||||
// Try resolve command available in $PATH
|
||||
if ((ckd = shelljs.which(app.script))) {
|
||||
if ((ckd = require('shelljs').which(app.script))) {
|
||||
if (typeof(ckd) !== 'string')
|
||||
ckd = ckd.toString();
|
||||
app.pm_exec_path = ckd;
|
||||
@ -226,7 +223,7 @@ Common.prepareAppConf = function(opts, app) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
Common.printError(cst.PREFIX_MSG_WARNING + 'Folder does not exists: ' + dir);
|
||||
Common.printOut(cst.PREFIX_MSG + 'Creating folder: ' + dir);
|
||||
mkdirp(dir, function(err) {
|
||||
require('mkdirp')(dir, function(err) {
|
||||
if (!err) return;
|
||||
Common.printError(cst.PREFIX_MSG_ERR + 'Could not create folder: ' + path.dirname(af));
|
||||
throw new Error('Could not create folder');
|
||||
@ -247,20 +244,22 @@ Common.prepareAppConf = function(opts, app) {
|
||||
* @param {string} filename
|
||||
* @return {mixed} null if not conf file, json or yaml if conf
|
||||
*/
|
||||
Common.isConfigFile = function(filename) {
|
||||
if (typeof(filename) != 'string')
|
||||
Common.isConfigFile = function (filename) {
|
||||
if (typeof (filename) !== 'string')
|
||||
return null;
|
||||
if (filename.indexOf('.json') != -1)
|
||||
if (filename.indexOf('.json') !== -1)
|
||||
return 'json';
|
||||
if (filename.indexOf('.yml') > -1 || filename.indexOf('.yaml') > -1)
|
||||
return 'yaml';
|
||||
if (filename.indexOf('.config.js') != -1)
|
||||
if (filename.indexOf('.config.js') !== -1)
|
||||
return 'js';
|
||||
if (filename.indexOf('.config.mjs') !== -1)
|
||||
return 'mjs';
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a config file like ecosystem.json. Supported formats: JS, JSON, JSON5, YAML.
|
||||
* Parses a config file like ecosystem.config.js. Supported formats: JS, JSON, JSON5, YAML.
|
||||
* @param {string} confString contents of the config file
|
||||
* @param {string} filename path to the config file
|
||||
* @return {Object} config object
|
||||
@ -288,7 +287,7 @@ Common.parseConfig = function(confObj, filename) {
|
||||
filename.indexOf('.yaml') > -1) {
|
||||
return yamljs.parse(confObj.toString());
|
||||
}
|
||||
else if (filename.indexOf('.config.js') > -1) {
|
||||
else if (filename.indexOf('.config.js') > -1 || filename.indexOf('.config.mjs') > -1) {
|
||||
var confPath = require.resolve(path.resolve(filename));
|
||||
delete require.cache[confPath];
|
||||
return require(confPath);
|
||||
@ -366,7 +365,7 @@ var resolveNodeInterpreter = function(app) {
|
||||
var nvm_cmd = '. ' + nvm_bin + ' ; nvm install ' + node_version;
|
||||
|
||||
Common.printOut(cst.PREFIX_MSG + 'Executing: %s', nvm_cmd);
|
||||
shelljs.exec(nvm_cmd);
|
||||
require('shelljs').exec(nvm_cmd);
|
||||
}
|
||||
|
||||
Common.printOut(cst.PREFIX_MSG + chalk.green.bold('Setting Node to v%s (path=%s)'),
|
||||
@ -409,7 +408,7 @@ Common.sink.resolveInterpreter = function(app) {
|
||||
app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/coffee');
|
||||
}
|
||||
|
||||
if (app.exec_interpreter != 'none' && shelljs.which(app.exec_interpreter) == null) {
|
||||
if (app.exec_interpreter != 'none' && require('shelljs').which(app.exec_interpreter) == null) {
|
||||
Common.printError(cst.PREFIX_MSG_ERR + 'Interpreter ' + app.exec_interpreter + ' does not seem to be available');
|
||||
}
|
||||
return app;
|
||||
@ -585,9 +584,34 @@ Common.verifyConfs = function(appConfs){
|
||||
for (var i = 0; i < appConfs.length; i++) {
|
||||
var app = appConfs[i];
|
||||
|
||||
if (app.disable_trace) {
|
||||
app.trace = false
|
||||
delete app.disable_trace;
|
||||
// JSON conf: alias cmd to script
|
||||
if (app.cmd && !app.script) {
|
||||
app.script = app.cmd
|
||||
delete app.cmd
|
||||
}
|
||||
// JSON conf: alias command to script
|
||||
if (app.command && !app.script) {
|
||||
app.script = app.command
|
||||
delete app.command
|
||||
}
|
||||
|
||||
app.username = Common.getCurrentUsername();
|
||||
|
||||
// If command is like pm2 start "python xx.py --ok"
|
||||
// Then automatically start the script with bash -c and set a name eq to command
|
||||
if (app.script && app.script.indexOf(' ') > -1) {
|
||||
var _script = app.script;
|
||||
if (require('shelljs').which('bash'))
|
||||
app.script = 'bash';
|
||||
else if (require('shelljs').which('sh'))
|
||||
app.script = 'sh';
|
||||
else
|
||||
throw new Error('bash and sh not available in $PATH')
|
||||
|
||||
app.args = ['-c', _script];
|
||||
if (!app.name) {
|
||||
app.name = _script
|
||||
}
|
||||
}
|
||||
|
||||
if ((app.uid || app.gid) && app.force !== true) {
|
||||
@ -597,25 +621,34 @@ Common.verifyConfs = function(appConfs){
|
||||
}
|
||||
}
|
||||
|
||||
// If no uid set and command runned as sudo, use the parent shell USER
|
||||
// to set it to his uid and not root
|
||||
// if (!app.uid && process.env.SUDO_USER) {
|
||||
// app.uid = process.env.SUDO_USER;
|
||||
// }
|
||||
// Specific options of PM2.io
|
||||
if (process.env.PM2_DEEP_MONITORING)
|
||||
app.deep_monitoring = true;
|
||||
|
||||
// Warn deprecates.
|
||||
checkDeprecates(app);
|
||||
if (app.disable_trace) {
|
||||
app.trace = false
|
||||
delete app.disable_trace;
|
||||
}
|
||||
|
||||
if (app.instances == 'max')
|
||||
app.instances = 0;
|
||||
// Sanity check, default to number of cores if value can't be parsed
|
||||
if (typeof(app.instances) === 'string')
|
||||
app.instances = parseInt(app.instances) || 0;
|
||||
|
||||
// Check Exec mode
|
||||
checkExecMode(app);
|
||||
|
||||
if (app.exec_mode != 'cluster_mode' &&
|
||||
!app.instances && typeof(app.merge_logs) == 'undefined')
|
||||
app.merge_logs = true;
|
||||
|
||||
// Render an app name if not existing.
|
||||
prepareAppName(app);
|
||||
|
||||
var ret = Config.validateJSON(app);
|
||||
//debug('After processing', ret);
|
||||
// Show errors if existing.
|
||||
|
||||
// Show errors if existing.
|
||||
if (ret.errors && ret.errors.length > 0){
|
||||
ret.errors.forEach(function(err){
|
||||
warn(err);
|
||||
@ -689,18 +722,6 @@ function checkExecMode(conf) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check deprecates and show warnings.
|
||||
* @param {Object} conf
|
||||
*/
|
||||
function checkDeprecates(conf){
|
||||
if (conf.instances == 'max')
|
||||
conf.instances = 0;
|
||||
// Sanity check, default to number of cores if value can't be parsed
|
||||
if (typeof(conf.instances) === 'string')
|
||||
conf.instances = parseInt(conf.instances) || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an app name if not existing.
|
||||
* @param {Object} conf
|
||||
|
||||
@ -153,7 +153,7 @@ Daemon.prototype.innerStart = function(cb) {
|
||||
var profiler;
|
||||
|
||||
try {
|
||||
profiler = require('v8-profiler');
|
||||
profiler = require('v8-profiler-node8');
|
||||
} catch(e) {
|
||||
profiler = null;
|
||||
}
|
||||
@ -231,7 +231,6 @@ Daemon.prototype.innerStart = function(cb) {
|
||||
notifyByProcessId : God.notifyByProcessId,
|
||||
|
||||
notifyKillPM2 : God.notifyKillPM2,
|
||||
forceGc : God.forceGc,
|
||||
monitor : God.monitor,
|
||||
unmonitor : God.unmonitor,
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ var numCPUs = require('os').cpus() ? require('os').cpus().length : 1;
|
||||
var path = require('path');
|
||||
var EventEmitter2 = require('eventemitter2').EventEmitter2;
|
||||
var fs = require('fs');
|
||||
var pidusage = require('pidusage');
|
||||
var vizion = require('vizion');
|
||||
var debug = require('debug')('pm2:god');
|
||||
var Utility = require('./Utility');
|
||||
@ -291,9 +290,6 @@ God.handleExit = function handleExit(clu, exit_code, kill_signal) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (proc.process.pid)
|
||||
pidusage.unmonitor(proc.process.pid);
|
||||
|
||||
var stopping = (proc.pm2_env.status == cst.STOPPING_STATUS
|
||||
|| proc.pm2_env.status == cst.STOPPED_STATUS
|
||||
|| proc.pm2_env.status == cst.ERRORED_STATUS) || (proc.pm2_env.autorestart === false ||
|
||||
@ -384,6 +380,9 @@ God.handleExit = function handleExit(clu, exit_code, kill_signal) {
|
||||
* Init new process
|
||||
*/
|
||||
God.prepare = function prepare (env, cb) {
|
||||
// generate a new unique id for each processes
|
||||
env.env.unique_id = Utility.generateUUID()
|
||||
|
||||
// if the app is standalone, no multiple instance
|
||||
if (typeof env.instances === 'undefined') {
|
||||
env.vizion_running = false;
|
||||
|
||||
@ -40,55 +40,63 @@ module.exports = function(God) {
|
||||
*/
|
||||
God.getMonitorData = function getMonitorData(env, cb) {
|
||||
var processes = God.getFormatedProcesses();
|
||||
var pids = processes.filter(filterBadProcess)
|
||||
.map(function(pro, i) {
|
||||
var pid = getProcessId(pro)
|
||||
return pid;
|
||||
})
|
||||
|
||||
async.eachSeries(processes, function computeMonitor(pro, next) {
|
||||
if (pro.pm2_env.status == cst.ONLINE_STATUS) {
|
||||
var pid = pro.pid;
|
||||
|
||||
if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) {
|
||||
if (isNaN(pro.pm2_env.axm_options.pid)) {
|
||||
pro['monit'] = {
|
||||
memory : 0,
|
||||
cpu : 0
|
||||
};
|
||||
return process.nextTick(next);
|
||||
}
|
||||
pid = pro.pm2_env.axm_options.pid;
|
||||
}
|
||||
|
||||
pidusage.stat(pid, function retPidUsage(err, res) {
|
||||
if (err) {
|
||||
// Do not log, some time modules does not retrieve PID
|
||||
// console.error('Error caught while calling pidusage');
|
||||
// console.error(err);
|
||||
pro['monit'] = {
|
||||
memory : 0,
|
||||
cpu : 0
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
pro['monit'] = {
|
||||
memory : Math.floor(res.memory),
|
||||
cpu : Math.floor(res.cpu)
|
||||
};
|
||||
res = null;
|
||||
pid = null;
|
||||
return next();
|
||||
});
|
||||
}
|
||||
else {
|
||||
// No pids, return empty statistics
|
||||
if (pids.length === 0) {
|
||||
return cb(null, processes.map(function(pro) {
|
||||
pro['monit'] = {
|
||||
memory : 0,
|
||||
cpu : 0
|
||||
};
|
||||
return next();
|
||||
}
|
||||
}, function retMonitor(err, res) {
|
||||
if (err) return cb(God.logAndGenerateError(err), null);
|
||||
return cb(null, processes);
|
||||
});
|
||||
|
||||
return pro
|
||||
}))
|
||||
}
|
||||
|
||||
pidusage(pids, function retPidUsage(err, statistics) {
|
||||
// Just log, we'll set empty statistics
|
||||
if (err) {
|
||||
console.error('Error caught while calling pidusage');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
processes = processes.map(function(pro) {
|
||||
if (filterBadProcess(pro) === false) {
|
||||
pro['monit'] = {
|
||||
memory : 0,
|
||||
cpu : 0
|
||||
};
|
||||
|
||||
return pro;
|
||||
}
|
||||
|
||||
var pid = getProcessId(pro);
|
||||
var stat = statistics[pid];
|
||||
|
||||
if (!stat) {
|
||||
pro['monit'] = {
|
||||
memory : 0,
|
||||
cpu : 0
|
||||
};
|
||||
|
||||
return pro;
|
||||
}
|
||||
|
||||
pro['monit'] = {
|
||||
memory: stat.memory,
|
||||
cpu: Math.round(stat.cpu * 10) / 10
|
||||
};
|
||||
|
||||
return pro;
|
||||
});
|
||||
|
||||
cb(null, processes);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -126,6 +134,7 @@ module.exports = function(God) {
|
||||
God.dumpProcessList = function(cb) {
|
||||
var process_list = [];
|
||||
var apps = Utility.clone(God.getFormatedProcesses());
|
||||
var that = this;
|
||||
|
||||
// Don't override the actual dump file if process list is empty
|
||||
// unless user explicitely did `pm2 dump`.
|
||||
@ -137,13 +146,25 @@ module.exports = function(God) {
|
||||
}
|
||||
|
||||
function fin(err) {
|
||||
|
||||
// try to fix issues with empty dump file
|
||||
// like #3485
|
||||
if (process_list.length === 0) {
|
||||
|
||||
// fix : if no dump file, no process, only module and after pm2 update
|
||||
if (!fs.existsSync(cst.DUMP_FILE_PATH)) {
|
||||
that.clearDump(function(){});
|
||||
}
|
||||
|
||||
// if no process in list don't modify dump file
|
||||
// process list should not be empty
|
||||
return cb(null, {success:true, process_list: process_list});
|
||||
}
|
||||
|
||||
// Back up dump file
|
||||
try {
|
||||
if (fs.existsSync(cst.DUMP_FILE_PATH)) {
|
||||
if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
|
||||
fs.unlinkSync(cst.DUMP_BACKUP_FILE_PATH);
|
||||
}
|
||||
fs.renameSync(cst.DUMP_FILE_PATH, cst.DUMP_BACKUP_FILE_PATH);
|
||||
fs.writeFileSync(cst.DUMP_BACKUP_FILE_PATH, fs.readFileSync(cst.DUMP_FILE_PATH));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack || e);
|
||||
@ -155,8 +176,13 @@ module.exports = function(God) {
|
||||
} catch (e) {
|
||||
console.error(e.stack || e);
|
||||
try {
|
||||
fs.unlinkSync(cst.DUMP_FILE_PATH);
|
||||
// try to backup file
|
||||
if(fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) {
|
||||
fsExtra.copySync(cst.DUMP_BACKUP_FILE_PATH, cst.DUMP_FILE_PATH);
|
||||
}
|
||||
} catch (e) {
|
||||
// don't keep broken file
|
||||
fs.unlinkSync(cst.DUMP_FILE_PATH);
|
||||
console.error(e.stack || e);
|
||||
}
|
||||
}
|
||||
@ -213,8 +239,13 @@ module.exports = function(God) {
|
||||
|
||||
var proc = Utility.clone(God.clusters_db[id].pm2_env);
|
||||
|
||||
|
||||
delete proc.created_at;
|
||||
delete proc.pm_id;
|
||||
delete proc.unique_id;
|
||||
|
||||
// generate a new unique id for new process
|
||||
proc.unique_id = Utility.generateUUID()
|
||||
|
||||
God.injectVariables(proc, function inject (_err, proc) {
|
||||
return God.executeApp(Utility.clone(proc), function (err, clu) {
|
||||
@ -264,17 +295,19 @@ module.exports = function(God) {
|
||||
if (!(id in God.clusters_db))
|
||||
return cb(God.logAndGenerateError(id + ' : id unknown'), {});
|
||||
|
||||
//clear time-out restart task
|
||||
clearTimeout(God.clusters_db[id].pm2_env.restart_task);
|
||||
|
||||
if (God.clusters_db[id].pm2_env.status == cst.STOPPED_STATUS)
|
||||
return cb(null, God.getFormatedProcess(id));
|
||||
// state == 'none' means that the process is not online yet
|
||||
if (God.clusters_db[id].state && God.clusters_db[id].state === 'none')
|
||||
return setTimeout(function() { God.stopProcessId(id, cb); }, 250);
|
||||
|
||||
var proc = God.clusters_db[id];
|
||||
|
||||
//clear time-out restart task
|
||||
clearTimeout(proc.pm2_env.restart_task);
|
||||
|
||||
if (proc.pm2_env.status == cst.STOPPED_STATUS) {
|
||||
proc.process.pid = 0;
|
||||
return cb(null, God.getFormatedProcess(id));
|
||||
}
|
||||
// state == 'none' means that the process is not online yet
|
||||
if (proc.state && proc.state === 'none')
|
||||
return setTimeout(function() { God.stopProcessId(id, cb); }, 250);
|
||||
|
||||
console.log('Stopping app:%s id:%s', proc.pm2_env.name, proc.pm2_env.pm_id);
|
||||
proc.pm2_env.status = cst.STOPPING_STATUS;
|
||||
|
||||
@ -286,7 +319,6 @@ module.exports = function(God) {
|
||||
|
||||
God.killProcess(proc.process.pid, proc.pm2_env, function(err) {
|
||||
proc.pm2_env.status = cst.STOPPED_STATUS;
|
||||
pidusage.unmonitor(proc.process.pid);
|
||||
|
||||
God.notify('exit', proc);
|
||||
|
||||
@ -823,3 +855,27 @@ module.exports = function(God) {
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
function filterBadProcess(pro) {
|
||||
if (pro.pm2_env.status !== cst.ONLINE_STATUS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) {
|
||||
if (isNaN(pro.pm2_env.axm_options.pid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getProcessId(pro) {
|
||||
var pid = pro.pid
|
||||
|
||||
if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) {
|
||||
pid = pro.pm2_env.axm_options.pid;
|
||||
}
|
||||
|
||||
return pid
|
||||
}
|
||||
|
||||
@ -167,9 +167,9 @@ module.exports = function(God) {
|
||||
clearInterval(timer);
|
||||
return cb(null, true);
|
||||
}
|
||||
console.log('pid=%d msg=failed to kill - retrying in 100ms', pid);
|
||||
console.log('pid=%d msg=failed to kill - retrying in %dms', pid, pm2_env.kill_retry_time);
|
||||
return false;
|
||||
}, 100);
|
||||
}, pm2_env.kill_retry_time);
|
||||
|
||||
timeout = setTimeout(function() {
|
||||
clearInterval(timer);
|
||||
@ -247,21 +247,4 @@ module.exports = function(God) {
|
||||
pm2_env.unstable_restarts = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method forcegc
|
||||
* @return
|
||||
*/
|
||||
God.forceGc = function(opts, cb) {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
debug('Garbage collection triggered successfully');
|
||||
if (cb) cb(null, {success: true});
|
||||
}
|
||||
else {
|
||||
debug('Garbage collection failed');
|
||||
if (cb) cb(null, {success: false});
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@ -174,7 +174,7 @@ function hardReload(God, id, wait_msg, cb) {
|
||||
module.exports = function(God) {
|
||||
|
||||
/**
|
||||
* GracefulReload
|
||||
* Reload
|
||||
* @method softReloadProcessId
|
||||
* @param {} id
|
||||
* @param {} cb
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var crypto = require('crypto');
|
||||
|
||||
const CIPHER_ALGORITHM = 'aes256';
|
||||
|
||||
var Cipher = module.exports = {};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method decipherMessage
|
||||
* @param {} msg
|
||||
* @return ret
|
||||
*/
|
||||
Cipher.decipherMessage = function(msg, key) {
|
||||
var ret = {};
|
||||
|
||||
try {
|
||||
var decipher = crypto.createDecipher(CIPHER_ALGORITHM, key);
|
||||
var decipheredMessage = decipher.update(msg, 'hex', 'utf8');
|
||||
decipheredMessage += decipher.final('utf8');
|
||||
ret = JSON.parse(decipheredMessage);
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method cipherMessage
|
||||
* @param {} data
|
||||
* @param {} key
|
||||
* @return
|
||||
*/
|
||||
Cipher.cipherMessage = function(data, key) {
|
||||
try {
|
||||
var cipher = crypto.createCipher(CIPHER_ALGORITHM, key);
|
||||
var cipheredData = cipher.update(data, 'utf8', 'hex');
|
||||
cipheredData += cipher.final('hex');
|
||||
return cipheredData;
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,443 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var ipm2 = require('./pm2-interface.js');
|
||||
var rpc = require('pm2-axon-rpc');
|
||||
var axon = require('pm2-axon');
|
||||
var debug = require('debug')('interface:driver'); // Interface
|
||||
var chalk = require('chalk');
|
||||
var Url = require('url');
|
||||
var os = require('os');
|
||||
var domain = require('domain');
|
||||
var fmt = require('../tools/fmt.js');
|
||||
var pkg = require('../../package.json');
|
||||
var PM2 = require('../..');
|
||||
|
||||
var cst = require('../../constants.js');
|
||||
var Cipher = require('./Cipher.js');
|
||||
var ReverseInteractor = require('./ReverseInteractor.js');
|
||||
var PushInteractor = require('./PushInteractor.js');
|
||||
var Utility = require('../Utility.js');
|
||||
var WatchDog = require('./WatchDog.js');
|
||||
var Conf = require('../Configuration.js');
|
||||
var HttpRequest = require('./HttpRequest.js');
|
||||
var InternalIP = require('./internal-ip.js');
|
||||
|
||||
global._pm2_password_protected = false;
|
||||
|
||||
// Flag for log streaming status
|
||||
global._logs = false;
|
||||
|
||||
var Daemon = module.exports = {
|
||||
connectToPM2 : function() {
|
||||
return ipm2();
|
||||
},
|
||||
exit : function() {
|
||||
var self = this;
|
||||
|
||||
this.opts.pm2_instance.disconnect(function() {
|
||||
console.log('Connection to PM2 via CLI closed');
|
||||
});
|
||||
|
||||
process.nextTick(function() {
|
||||
try {
|
||||
fs.unlinkSync(cst.INTERACTOR_RPC_PORT);
|
||||
fs.unlinkSync(cst.INTERACTOR_PID_PATH);
|
||||
} catch(e) {}
|
||||
|
||||
if (self.opts.ipm2)
|
||||
self.opts.ipm2.disconnect();
|
||||
|
||||
console.log('Exiting Interactor');
|
||||
|
||||
if (!this._rpc || !this._rpc.sock)
|
||||
return process.exit(cst.ERROR_EXIT);
|
||||
|
||||
this._rpc.sock.close(function() {
|
||||
console.log('RPC closed - Interactor killed');
|
||||
process.exit(cst.SUCCESS_EXIT);
|
||||
});
|
||||
});
|
||||
},
|
||||
activateRPC : function() {
|
||||
console.log('Launching Interactor exposure');
|
||||
|
||||
var self = this;
|
||||
var rep = axon.socket('rep');
|
||||
var daemon_server = new rpc.Server(rep);
|
||||
var sock = rep.bind(cst.INTERACTOR_RPC_PORT);
|
||||
|
||||
daemon_server.expose({
|
||||
kill : function(cb) {
|
||||
console.log('Killing interactor');
|
||||
cb(null);
|
||||
return Daemon.exit();
|
||||
},
|
||||
passwordSet : function(cb) {
|
||||
global._pm2_password_protected = true;
|
||||
return cb(null);
|
||||
},
|
||||
getInfos : function(cb) {
|
||||
if (self.opts &&
|
||||
self.opts.DAEMON_ACTIVE == true)
|
||||
return cb(null, {
|
||||
machine_name : self.opts.MACHINE_NAME,
|
||||
public_key : self.opts.PUBLIC_KEY,
|
||||
secret_key : self.opts.SECRET_KEY,
|
||||
remote_host : cst.REMOTE_HOST,
|
||||
remote_port : cst.REMOTE_PORT,
|
||||
reverse_interaction : self.opts.REVERSE_INTERACT,
|
||||
socket_path : cst.INTERACTOR_RPC_PORT,
|
||||
pm2_home_monitored : cst.PM2_HOME
|
||||
});
|
||||
else {
|
||||
return cb(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
return daemon_server;
|
||||
},
|
||||
formatMetada : function() {
|
||||
var cpu, memory;
|
||||
|
||||
var self = this;
|
||||
|
||||
try {
|
||||
cpu = os.cpus();
|
||||
memory = Math.floor(os.totalmem() / 1024 / 1024);
|
||||
} catch(e) {
|
||||
cpu = 0;
|
||||
memory = 0;
|
||||
};
|
||||
|
||||
var ciphered_data = Cipher.cipherMessage(JSON.stringify({
|
||||
MACHINE_NAME : this.opts.MACHINE_NAME,
|
||||
PUBLIC_KEY : this.opts.PUBLIC_KEY,
|
||||
PM2_VERSION : this.opts.PM2_VERSION,
|
||||
RECYCLE : this.opts.RECYCLE || false,
|
||||
MEMORY : memory,
|
||||
HOSTNAME : os.hostname(),
|
||||
CPUS : cpu.length
|
||||
}), this.opts.SECRET_KEY);
|
||||
|
||||
return ciphered_data;
|
||||
},
|
||||
pingKeepAlive : function() {
|
||||
var self = this;
|
||||
|
||||
(function checkInternet() {
|
||||
require('dns').lookup('google.com',function(err) {
|
||||
if (err && (err.code == 'ENOTFOUND' || err.code == 'EAI_AGAIN')) {
|
||||
if (self.opts._connection_is_up == true)
|
||||
console.error('[CRITICAL] Internet is unreachable (via DNS lookup strategy)');
|
||||
self.opts._connection_is_up = false;
|
||||
} else {
|
||||
if (self.opts._connection_is_up == false) {
|
||||
console.log('[TENTATIVE] Reactivating connection');
|
||||
PushInteractor.connectRemote();
|
||||
ReverseInteractor.reconnect();
|
||||
}
|
||||
self.opts._connection_is_up = true;
|
||||
}
|
||||
setTimeout(checkInternet, 15000);
|
||||
});
|
||||
})();
|
||||
},
|
||||
changeUrls : function(push_url, reverse) {
|
||||
if (push_url)
|
||||
PushInteractor.connectRemote(push_url);
|
||||
if (reverse)
|
||||
ReverseInteractor.changeUrl(reverse);
|
||||
},
|
||||
refreshWorker : function() {
|
||||
var self = this;
|
||||
|
||||
function refreshMetadata() {
|
||||
var ciphered_data = Daemon.formatMetada();
|
||||
|
||||
HttpRequest.post({
|
||||
url : self.opts.ROOT_URL,
|
||||
port : self.opts.ROOT_PORT,
|
||||
data : {
|
||||
public_id : self.opts.PUBLIC_KEY,
|
||||
data : ciphered_data
|
||||
}
|
||||
}, function(err, km_data) {
|
||||
if (err) return console.error(err);
|
||||
|
||||
/** protect against malformated data **/
|
||||
if (!km_data ||
|
||||
!km_data.endpoints ||
|
||||
!km_data.endpoints.push ||
|
||||
!km_data.endpoints.reverse) {
|
||||
console.error('[CRITICAL] Malformated data received, skipping...');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************
|
||||
* Urls has changed = update workers *
|
||||
**************************************/
|
||||
|
||||
if ((Daemon.current_km_data.endpoints.push != km_data.endpoints.push) ||
|
||||
(Daemon.current_km_data.endpoints.reverse != km_data.endpoints.reverse)) {
|
||||
self.changeUrls(km_data.endpoints.push, km_data.endpoints.reverse);
|
||||
Daemon.current_km_data = km_data;
|
||||
}
|
||||
else {
|
||||
debug('[REFRESH META] No need to update URL (same)', km_data);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Refresh metadata every minutes
|
||||
setInterval(function() {
|
||||
refreshMetadata();
|
||||
}, 60000);
|
||||
},
|
||||
validateData : function() {
|
||||
var opts = {};
|
||||
|
||||
opts.MACHINE_NAME = process.env.PM2_MACHINE_NAME;
|
||||
opts.PUBLIC_KEY = process.env.PM2_PUBLIC_KEY;
|
||||
opts.SECRET_KEY = process.env.PM2_SECRET_KEY;
|
||||
opts.RECYCLE = process.env.KM_RECYCLE ? JSON.parse(process.env.KM_RECYCLE) : false;
|
||||
opts.REVERSE_INTERACT = JSON.parse(process.env.PM2_REVERSE_INTERACT);
|
||||
opts.PM2_VERSION = pkg.version;
|
||||
|
||||
if (!opts.MACHINE_NAME) {
|
||||
console.error('You must provide a PM2_MACHINE_NAME environment variable');
|
||||
process.exit(cst.ERROR_EXIT);
|
||||
}
|
||||
else if (!opts.PUBLIC_KEY) {
|
||||
console.error('You must provide a PM2_PUBLIC_KEY environment variable');
|
||||
process.exit(cst.ERROR_EXIT);
|
||||
}
|
||||
else if (!opts.SECRET_KEY) {
|
||||
console.error('You must provide a PM2_SECRET_KEY environment variable');
|
||||
process.exit(cst.ERROR_EXIT);
|
||||
}
|
||||
return opts;
|
||||
},
|
||||
welcome : function(cb) {
|
||||
var self = this;
|
||||
var ciphered_data = Daemon.formatMetada();
|
||||
|
||||
if (!ciphered_data) {
|
||||
process.send({
|
||||
msg : 'Error while ciphering data',
|
||||
error : true
|
||||
});
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
var retries = 0;
|
||||
|
||||
function doWelcomeQuery(cb) {
|
||||
HttpRequest.post({
|
||||
url : self.opts.ROOT_URL,
|
||||
data : {
|
||||
public_id : self.opts.PUBLIC_KEY,
|
||||
data : ciphered_data
|
||||
}
|
||||
}, function(err, km_data) {
|
||||
self.current_km_data = km_data;
|
||||
if (err) {
|
||||
console.error('Got error while connecting: %s', err.message || err);
|
||||
|
||||
if (retries < 30) {
|
||||
retries++;
|
||||
|
||||
setTimeout(function() {
|
||||
doWelcomeQuery(cb);
|
||||
}, 200 * retries);
|
||||
return false;
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (self.opts.RECYCLE) {
|
||||
if (!km_data.name) {
|
||||
console.error('Error no previous machine name for recycle option returned!');
|
||||
}
|
||||
self.opts.MACHINE_NAME = km_data.name;
|
||||
};
|
||||
|
||||
// For Human feedback
|
||||
if (process.send) {
|
||||
try {
|
||||
process.send({
|
||||
error : false,
|
||||
km_data : km_data,
|
||||
online : true,
|
||||
pid : process.pid,
|
||||
machine_name : self.opts.MACHINE_NAME,
|
||||
public_key : self.opts.PUBLIC_KEY,
|
||||
secret_key : self.opts.SECRET_KEY,
|
||||
reverse_interaction : self.opts.REVERSE_INTERACT
|
||||
});
|
||||
} catch(e) {
|
||||
// Just in case the CLI has been disconected
|
||||
}
|
||||
}
|
||||
// Return get data
|
||||
return cb(null, km_data);
|
||||
})
|
||||
}
|
||||
|
||||
doWelcomeQuery(function(err, meta) {
|
||||
return cb(err, meta);
|
||||
});
|
||||
},
|
||||
protectedStart : function() {
|
||||
var self = this;
|
||||
var d = domain.create();
|
||||
|
||||
d.once('error', function(err) {
|
||||
fmt.sep();
|
||||
fmt.title('Agent global error caught');
|
||||
fmt.field('Time', new Date());
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
fmt.sep();
|
||||
|
||||
console.error('[Agent] Resurrecting');
|
||||
|
||||
var KMDaemon = require('../Interactor/InteractorDaemonizer');
|
||||
|
||||
KMDaemon.rescueStart(cst, function(err, dt) {
|
||||
if (err) {
|
||||
console.error('[Agent] Failed to rescue agent, error:');
|
||||
console.error(err.message || err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('[Agent] Rescued.');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
d.run(function() {
|
||||
self.start();
|
||||
});
|
||||
},
|
||||
start : function() {
|
||||
var self = this;
|
||||
|
||||
self.opts = self.validateData();
|
||||
self.opts.ipm2 = null;
|
||||
self.opts.internal_ip = InternalIP();
|
||||
self.opts.pm2_instance = PM2;
|
||||
self.opts._connection_is_up = true;
|
||||
self.current_km_data = null;
|
||||
|
||||
self.opts.pm2_instance.connect(function() {
|
||||
console.log('Connected to PM2');
|
||||
});
|
||||
|
||||
self._rpc = self.activateRPC();
|
||||
|
||||
// Test mode #1
|
||||
if (cst.DEBUG) {
|
||||
self.opts.ROOT_URL = '127.0.0.1';
|
||||
if (process.env.NODE_ENV == 'test')
|
||||
self.opts.ROOT_PORT = 3400;
|
||||
else
|
||||
self.opts.ROOT_PORT = 3000;
|
||||
}
|
||||
else {
|
||||
self.opts.ROOT_URL = cst.KEYMETRICS_ROOT_URL;
|
||||
}
|
||||
|
||||
if (Conf.getSync('pm2:passwd'))
|
||||
global._pm2_password_protected = true;
|
||||
|
||||
// Test mode #2
|
||||
if (process.env.NODE_ENV == 'local_test') {
|
||||
self.opts.DAEMON_ACTIVE = true;
|
||||
|
||||
self.opts.ipm2 = self.connectToPM2();
|
||||
|
||||
PushInteractor.start({
|
||||
url : 'http://127.0.0.1:4321',
|
||||
conf : self.opts
|
||||
});
|
||||
|
||||
ReverseInteractor.start({
|
||||
url : 'http://127.0.0.1:4322',
|
||||
conf : self.opts
|
||||
});
|
||||
if (process.send)
|
||||
process.send({
|
||||
success : true,
|
||||
debug : true
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
Daemon.welcome(function(err, km_data) {
|
||||
if (err) {
|
||||
if (process.send)
|
||||
process.send({
|
||||
error : true,
|
||||
msg : err.stack || err
|
||||
});
|
||||
console.log(err.stack || err);
|
||||
return Daemon.exit();
|
||||
}
|
||||
|
||||
if (km_data.disabled == true) {
|
||||
console.error('Interactor disabled');
|
||||
return Daemon.exit();
|
||||
}
|
||||
if (km_data.pending == true) {
|
||||
console.error('Interactor pending');
|
||||
return Daemon.exit();
|
||||
}
|
||||
|
||||
if (km_data.active == true) {
|
||||
self.opts.DAEMON_ACTIVE = true;
|
||||
|
||||
self.opts.ipm2 = self.connectToPM2();
|
||||
|
||||
WatchDog.start({
|
||||
conf : self.opts
|
||||
});
|
||||
|
||||
PushInteractor.start({
|
||||
url : km_data.endpoints.push,
|
||||
conf : self.opts
|
||||
});
|
||||
|
||||
if (self.opts.REVERSE_INTERACT == true) {
|
||||
ReverseInteractor.start({
|
||||
url : km_data.endpoints.reverse,
|
||||
conf : self.opts
|
||||
});
|
||||
}
|
||||
Daemon.refreshWorker();
|
||||
Daemon.pingKeepAlive();
|
||||
}
|
||||
else {
|
||||
console.log('Nothing to do, exiting');
|
||||
Daemon.exit();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* MAIN
|
||||
*/
|
||||
if (require.main === module) {
|
||||
console.log(chalk.cyan.bold('[Keymetrics.io]') + ' Launching agent');
|
||||
process.title = 'PM2: KM Agent (' + process.env.PM2_HOME + ')';
|
||||
|
||||
Utility.overrideConsole();
|
||||
Daemon.protectedStart();
|
||||
}
|
||||
@ -1,124 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file Filter process and system data to be sent to server
|
||||
* @author Alexandre Strzelewicz <strzelewicz.alexandre@gmail.com>
|
||||
* @project Interface
|
||||
*/
|
||||
|
||||
var os = require('os');
|
||||
|
||||
var cpu_info = {
|
||||
number : 0,
|
||||
info : 'no-data'
|
||||
};
|
||||
|
||||
try {
|
||||
cpu_info = {
|
||||
number : os.cpus().length,
|
||||
info : os.cpus()[0].model
|
||||
};
|
||||
} catch(e) {
|
||||
}
|
||||
|
||||
var SERVER_META = {
|
||||
totalMem : os.totalmem(),
|
||||
hostname : os.hostname(),
|
||||
type : os.type(),
|
||||
platform : os.platform(),
|
||||
arch : os.arch()
|
||||
};
|
||||
|
||||
var Filter = {};
|
||||
|
||||
Filter.getProcessID = function(machine_name, name, id) {
|
||||
return machine_name + ':' + name + ':' + id;
|
||||
};
|
||||
|
||||
Filter.machineSnapshot = function(processes, conf) {
|
||||
if (!processes) return null;
|
||||
|
||||
var filter_procs = [];
|
||||
|
||||
processes.forEach(function(proc) {
|
||||
if (proc.pm2_env.pm_id.toString().indexOf('_old_') == -1)
|
||||
filter_procs.push({
|
||||
pid : proc.pid,
|
||||
name : proc.pm2_env.name,
|
||||
interpreter : proc.pm2_env.exec_interpreter,
|
||||
restart_time : proc.pm2_env.restart_time,
|
||||
created_at : proc.pm2_env.created_at,
|
||||
exec_mode : proc.pm2_env.exec_mode,
|
||||
watching : proc.pm2_env.watch,
|
||||
pm_uptime : proc.pm2_env.pm_uptime,
|
||||
status : proc.pm2_env.status,
|
||||
pm_id : proc.pm2_env.pm_id,
|
||||
|
||||
cpu : Math.floor(proc.monit.cpu) || 0,
|
||||
memory : Math.floor(proc.monit.memory) || 0,
|
||||
|
||||
versioning : proc.pm2_env.versioning || null,
|
||||
|
||||
node_env : proc.pm2_env.NODE_ENV || null,
|
||||
|
||||
axm_actions : proc.pm2_env.axm_actions || [],
|
||||
axm_monitor : proc.pm2_env.axm_monitor || {},
|
||||
axm_options : proc.pm2_env.axm_options || {},
|
||||
axm_dynamic : proc.pm2_env.dynamic || {}
|
||||
});
|
||||
});
|
||||
|
||||
var node_version = process.version || '';
|
||||
|
||||
if (node_version != '') {
|
||||
if (node_version.indexOf('v1.') === 0 || node_version.indexOf('v2.') === 0 || node_version.indexOf('v3.') === 0)
|
||||
node_version = 'iojs ' + node_version;
|
||||
}
|
||||
var username = process.env.SUDO_USER || process.env.C9_USER || process.env.LOGNAME ||
|
||||
process.env.USER || process.env.LNAME || process.env.USERNAME;
|
||||
|
||||
return {
|
||||
process : filter_procs,
|
||||
server : {
|
||||
loadavg : os.loadavg(),
|
||||
total_mem : SERVER_META.totalMem,
|
||||
free_mem : os.freemem(),
|
||||
cpu : cpu_info,
|
||||
hostname : SERVER_META.hostname,
|
||||
uptime : os.uptime(),
|
||||
type : SERVER_META.type,
|
||||
platform : SERVER_META.platform,
|
||||
arch : SERVER_META.arch,
|
||||
user : username,
|
||||
interaction : conf.REVERSE_INTERACT,
|
||||
pm2_version : conf.PM2_VERSION,
|
||||
node_version : node_version
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Filter.monitoring = function(processes, conf) {
|
||||
if (!processes) return null;
|
||||
|
||||
var filter_procs = {};
|
||||
|
||||
processes.forEach(function(proc) {
|
||||
filter_procs[Filter.getProcessID(conf.MACHINE_NAME, proc.pm2_env.name,proc.pm2_env.pm_id)] = [
|
||||
Math.floor(proc.monit.cpu),
|
||||
Math.floor(proc.monit.memory)
|
||||
];
|
||||
});
|
||||
|
||||
return {
|
||||
loadavg : os.loadavg(),
|
||||
total_mem : SERVER_META.totalMem,
|
||||
free_mem : os.freemem(),
|
||||
processes : filter_procs
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Filter;
|
||||
@ -1,86 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var url = require('url')
|
||||
var debug = require('debug')('interface:http');
|
||||
|
||||
var HttpRequest = module.exports = {};
|
||||
|
||||
HttpRequest.post = function(opts, cb) {
|
||||
if (!(opts.data && opts.url)) {
|
||||
return cb({
|
||||
msg: 'missing parameters',
|
||||
port: opts.port,
|
||||
data: opts.data,
|
||||
url: opts.url
|
||||
})
|
||||
}
|
||||
|
||||
if (!opts.port) {
|
||||
var parsed = url.parse(opts.url)
|
||||
if (parsed.hostname && parsed.port) {
|
||||
opts.port = parseInt(parsed.port)
|
||||
opts.url = parsed.hostname
|
||||
} else {
|
||||
opts.port = 443
|
||||
}
|
||||
}
|
||||
|
||||
var options = {
|
||||
hostname: opts.url,
|
||||
path: '/api/node/verifyPM2',
|
||||
method: 'POST',
|
||||
port: opts.port,
|
||||
rejectUnauthorized: false,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(JSON.stringify(opts.data))
|
||||
}
|
||||
}
|
||||
|
||||
var client = (opts.port === 443) ? https : http;
|
||||
|
||||
var req = client.request(options, function(res){
|
||||
var dt = '';
|
||||
|
||||
res.on('data', function (chunk) {
|
||||
dt += chunk;
|
||||
});
|
||||
|
||||
res.on('end',function(){
|
||||
try {
|
||||
cb(null, JSON.parse(dt));
|
||||
} catch(e) {
|
||||
cb(e);
|
||||
}
|
||||
});
|
||||
|
||||
res.on('error', function(e){
|
||||
cb(e);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('socket', function (socket) {
|
||||
/**
|
||||
* Configure request timeout
|
||||
*/
|
||||
socket.setTimeout(7000);
|
||||
socket.on('timeout', function() {
|
||||
debug('Connection timeout when retrieveing PM2 metadata', options);
|
||||
req.abort();
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', function(e) {
|
||||
cb(e);
|
||||
});
|
||||
|
||||
req.write(JSON.stringify(opts.data));
|
||||
|
||||
req.end();
|
||||
};
|
||||
@ -1,535 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var debug = require('debug')('pm2:interface:daemon');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
var rpc = require('pm2-axon-rpc');
|
||||
var axon = require('pm2-axon');
|
||||
var chalk = require('chalk');
|
||||
var os = require('os');
|
||||
var cst = require('../../constants.js');
|
||||
var Common = require('../Common');
|
||||
var json5 = require('../tools/json5.js');
|
||||
var UX = require('../API/CliUx.js');
|
||||
|
||||
var InteractorDaemonizer = module.exports = {};
|
||||
|
||||
InteractorDaemonizer.rpc = {};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method ping
|
||||
* @param {} cb
|
||||
* @return
|
||||
*/
|
||||
InteractorDaemonizer.ping = function(conf, cb) {
|
||||
var req = axon.socket('req');
|
||||
var client = new rpc.Client(req);
|
||||
|
||||
debug('[PING INTERACTOR] Trying to connect to Interactor daemon');
|
||||
|
||||
client.sock.once('reconnect attempt', function() {
|
||||
client.sock.close();
|
||||
debug('Interactor Daemon not launched');
|
||||
return cb(false);
|
||||
});
|
||||
|
||||
client.sock.once('connect', function() {
|
||||
client.sock.once('close', function() {
|
||||
return cb(true);
|
||||
});
|
||||
client.sock.close();
|
||||
debug('Interactor Daemon alive');
|
||||
});
|
||||
|
||||
client.sock.once('error', function(e) {
|
||||
if (e.code == 'EACCES') {
|
||||
fs.stat(conf.INTERACTOR_RPC_PORT, function(e, stats) {
|
||||
if (stats.uid === 0) {
|
||||
console.error(conf.PREFIX_MSG_ERR + 'Permission denied, activate current user:');
|
||||
console.log(chalk.bold('$sudo chown ' + process.env.USER + ':' + process.env.USER + ' ' + conf.INTERACTOR_RPC_PORT));
|
||||
return process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
req.connect(conf.INTERACTOR_RPC_PORT);
|
||||
};
|
||||
|
||||
InteractorDaemonizer.killInteractorDaemon = function(conf, cb) {
|
||||
process.env.PM2_INTERACTOR_PROCESSING = true;
|
||||
|
||||
debug('Killing interactor #1 ping');
|
||||
InteractorDaemonizer.ping(conf, function(online) {
|
||||
debug('Interactor online', online);
|
||||
|
||||
if (!online) {
|
||||
if (!cb) Common.printError('Interactor not launched');
|
||||
|
||||
return cb(new Error('Interactor not launched'));
|
||||
}
|
||||
|
||||
InteractorDaemonizer.launchRPC(conf, function(err, data) {
|
||||
if (err) {
|
||||
setTimeout(function() {
|
||||
InteractorDaemonizer.disconnectRPC(cb);
|
||||
}, 100);
|
||||
return false;
|
||||
}
|
||||
InteractorDaemonizer.rpc.kill(function(err) {
|
||||
if (err) Common.printError(err);
|
||||
setTimeout(function() {
|
||||
InteractorDaemonizer.disconnectRPC(cb);
|
||||
}, 100);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method launchRPC
|
||||
* @param {} cb
|
||||
* @return
|
||||
*/
|
||||
InteractorDaemonizer.launchRPC = function(conf, cb) {
|
||||
var self = this;
|
||||
var req = axon.socket('req');
|
||||
this.client = new rpc.Client(req);
|
||||
|
||||
debug('Generating methods');
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method generateMethods
|
||||
* @param {} cb
|
||||
* @return
|
||||
*/
|
||||
var generateMethods = function(cb) {
|
||||
self.client.methods(function(err, methods) {
|
||||
Object.keys(methods).forEach(function(key) {
|
||||
var method_signature = methods[key];
|
||||
debug('+-- Creating %s method', method_signature.name);
|
||||
|
||||
(function(name) {
|
||||
/**
|
||||
* Description
|
||||
* @method name
|
||||
* @return
|
||||
*/
|
||||
self.rpc[name] = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift(name);
|
||||
self.client.call.apply(self.client, args);
|
||||
};
|
||||
})(method_signature.name);
|
||||
|
||||
});
|
||||
return cb();
|
||||
});
|
||||
};
|
||||
|
||||
this.client.sock.once('reconnect attempt', function(e) {
|
||||
self.client.sock.removeAllListeners();
|
||||
return cb({success:false, msg:'reconnect attempt'});
|
||||
});
|
||||
|
||||
this.client.sock.once('error', function(e) {
|
||||
console.error('Error in error catch all on Interactor');
|
||||
console.error(e.stack || e);
|
||||
});
|
||||
|
||||
this.client.sock.once('connect', function() {
|
||||
self.client.sock.removeAllListeners();
|
||||
generateMethods(function() {
|
||||
debug('Methods generated');
|
||||
cb(null, {success:true});
|
||||
});
|
||||
});
|
||||
|
||||
this.client_sock = req.connect(conf.INTERACTOR_RPC_PORT);
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method launchOrAttach
|
||||
* @param {} secret_key
|
||||
* @param {} public_key
|
||||
* @param {} machine_name
|
||||
* @param {} cb
|
||||
* @return
|
||||
*/
|
||||
function launchOrAttach(conf, infos, cb) {
|
||||
InteractorDaemonizer.ping(conf, function(online) {
|
||||
if (online) {
|
||||
debug('Interactor online, restarting it...');
|
||||
InteractorDaemonizer.launchRPC(conf, function() {
|
||||
InteractorDaemonizer.rpc.kill(function(err) {
|
||||
daemonize(conf, infos, function(err, msg, proc) {
|
||||
return cb(err, msg, proc);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
debug('Interactor offline, launching it...');
|
||||
daemonize(conf, infos, function(err, msg, proc) {
|
||||
return cb(err, msg, proc);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method daemonize
|
||||
* @param {} secret_key
|
||||
* @param {} public_key
|
||||
* @param {} machine_name
|
||||
* @param {} cb
|
||||
* @return
|
||||
*/
|
||||
var daemonize = function(conf, infos, cb) {
|
||||
var InteractorJS = path.resolve(path.dirname(module.filename), 'Daemon.js');
|
||||
|
||||
var out = null;
|
||||
var err = null;
|
||||
|
||||
if (process.env.TRAVIS || process.env.NODE_ENV == 'local_test') {
|
||||
// Redirect PM2 internal err and out
|
||||
// to STDERR STDOUT when running with Travis
|
||||
out = 1;
|
||||
err = 2;
|
||||
}
|
||||
else {
|
||||
out = fs.openSync(conf.INTERACTOR_LOG_FILE_PATH, 'a');
|
||||
err = fs.openSync(conf.INTERACTOR_LOG_FILE_PATH, 'a');
|
||||
}
|
||||
|
||||
var child = require('child_process').spawn('node', [InteractorJS], {
|
||||
silent : false,
|
||||
detached : true,
|
||||
cwd : process.cwd(),
|
||||
env : util._extend({
|
||||
PM2_HOME : conf.PM2_HOME,
|
||||
PM2_MACHINE_NAME : infos.machine_name,
|
||||
PM2_SECRET_KEY : infos.secret_key,
|
||||
PM2_PUBLIC_KEY : infos.public_key,
|
||||
PM2_REVERSE_INTERACT : infos.reverse_interact,
|
||||
KEYMETRICS_NODE : infos.info_node
|
||||
}, process.env),
|
||||
stdio : ['ipc', out, err]
|
||||
});
|
||||
|
||||
console.log('[KM] Connecting');
|
||||
|
||||
fs.writeFileSync(conf.INTERACTOR_PID_PATH, child.pid);
|
||||
|
||||
function onError(msg) {
|
||||
debug('Error when launching Interactor, please check the agent logs');
|
||||
return cb(msg);
|
||||
}
|
||||
|
||||
child.once('error', onError);
|
||||
|
||||
child.unref();
|
||||
|
||||
var t = setTimeout(function() {
|
||||
Common.printOut(cst.PREFIX_MSG_WARNING + ' Not managed to connect to Keymetrics, retrying in background. (check %s)', cst.INTERACTOR_LOG_FILE_PATH);
|
||||
child.removeAllListeners('message');
|
||||
child.removeAllListeners('error');
|
||||
child.disconnect();
|
||||
return cb(null, {}, child);
|
||||
}, 7000);
|
||||
|
||||
child.once('message', function(msg) {
|
||||
clearTimeout(t);
|
||||
debug('Interactor daemon launched', msg);
|
||||
|
||||
if (msg.debug) {
|
||||
return cb(null, msg, child);
|
||||
}
|
||||
|
||||
child.removeAllListeners('error');
|
||||
child.disconnect();
|
||||
|
||||
/*****************
|
||||
* Error messages
|
||||
*/
|
||||
if (msg.error == true) {
|
||||
console.log(chalk.red('[Keymetrics.io][ERROR]'), msg.msg);
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' Contact support contact@keymetrics.io and send us the error message');
|
||||
return cb(msg);
|
||||
}
|
||||
|
||||
if (msg.km_data.disabled == true) {
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' Server DISABLED BY ADMINISTRATION contact support contact@keymetrics.io with reference to your public and secret keys)');
|
||||
return cb(msg);
|
||||
}
|
||||
|
||||
if (msg.km_data.error == true) {
|
||||
console.log(chalk.red('[Keymetrics.io][ERROR]') + ' ' + msg.km_data.msg + ' (Public: %s) (Secret: %s) (Machine name: %s)', msg.public_key, msg.secret_key, msg.machine_name);
|
||||
return cb(msg);
|
||||
}
|
||||
|
||||
else if (msg.km_data.active == false && msg.km_data.pending == true) {
|
||||
console.log(chalk.red('[Keymetrics.io]') + ' ' + chalk.bold.red('Agent PENDING') + ' - Web Access: https://app.keymetrics.io/');
|
||||
console.log(chalk.red('[Keymetrics.io]') + ' You must upgrade your bucket in order to monitor more servers.');
|
||||
|
||||
return cb(msg);
|
||||
}
|
||||
|
||||
if (msg.km_data.active == true) {
|
||||
console.log(chalk.green.bold('[Monitoring Enabled]') + ' Dashboard access: https://app.keymetrics.io/#/r/%s', msg.public_key);
|
||||
return cb(null, msg, child);
|
||||
}
|
||||
|
||||
return cb(null, msg, child);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
InteractorDaemonizer.update = function(conf, cb) {
|
||||
InteractorDaemonizer.ping(conf, function(online) {
|
||||
if (!online) {
|
||||
Common.printError('Interactor not launched');
|
||||
return cb(new Error('Interactor not launched'));
|
||||
}
|
||||
InteractorDaemonizer.launchRPC(conf, function() {
|
||||
InteractorDaemonizer.rpc.kill(function(err) {
|
||||
if (err) {
|
||||
Common.printError(err);
|
||||
return cb(new Error(err));
|
||||
}
|
||||
Common.printOut('Interactor successfully killed');
|
||||
setTimeout(function() {
|
||||
InteractorDaemonizer.launchAndInteract(conf, {}, function() {
|
||||
return cb(null, {msg : 'Daemon launched'});
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get/Update/Merge agent configuration
|
||||
* @param {object} _infos
|
||||
*/
|
||||
InteractorDaemonizer.getOrSetConf = function(conf, infos, cb) {
|
||||
var reverse_interact = true;
|
||||
var version_management_active = true;
|
||||
var version_management_password = null;
|
||||
var secret_key;
|
||||
var public_key;
|
||||
var machine_name;
|
||||
var info_node;
|
||||
var new_connection = false;
|
||||
|
||||
// 1# Load configuration file
|
||||
try {
|
||||
var interaction_conf = json5.parse(fs.readFileSync(conf.INTERACTION_CONF));
|
||||
|
||||
public_key = interaction_conf.public_key;
|
||||
machine_name = interaction_conf.machine_name;
|
||||
secret_key = interaction_conf.secret_key;
|
||||
info_node = interaction_conf.info_node;
|
||||
|
||||
reverse_interact = interaction_conf.reverse_interact || true;
|
||||
|
||||
if (interaction_conf.version_management) {
|
||||
version_management_password = interaction_conf.version_management.password || version_management_password;
|
||||
version_management_active = interaction_conf.version_management.active || version_management_active;
|
||||
}
|
||||
} catch (e) {
|
||||
debug('Interaction file does not exists');
|
||||
}
|
||||
|
||||
// 2# Override with passed informations
|
||||
if (infos) {
|
||||
if (infos.secret_key)
|
||||
secret_key = infos.secret_key;
|
||||
|
||||
if (infos.public_key)
|
||||
public_key = infos.public_key;
|
||||
|
||||
if (infos.machine_name)
|
||||
machine_name = infos.machine_name;
|
||||
|
||||
if (infos.info_node)
|
||||
info_node = infos.info_node;
|
||||
|
||||
new_connection = true;
|
||||
}
|
||||
|
||||
// 3# Override with environment variables (highest-priority conf)
|
||||
if (process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET)
|
||||
secret_key = process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET;
|
||||
|
||||
if (process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC)
|
||||
public_key = process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC;
|
||||
|
||||
if (new_connection && info_node == null)
|
||||
info_node = process.env.KEYMETRICS_NODE || cst.KEYMETRICS_ROOT_URL;
|
||||
|
||||
if (!info_node)
|
||||
info_node = cst.KEYMETRICS_ROOT_URL;
|
||||
|
||||
if (!secret_key)
|
||||
return cb(new Error('secret key is not defined'));
|
||||
|
||||
if (!public_key)
|
||||
return cb(new Error('public key is not defined'));
|
||||
|
||||
if (!machine_name)
|
||||
machine_name = os.hostname() + '-' + require('crypto').randomBytes(4).toString('hex');
|
||||
|
||||
/**
|
||||
* Write new data to configuration file
|
||||
*/
|
||||
try {
|
||||
var new_interaction_conf = {
|
||||
secret_key : secret_key,
|
||||
public_key : public_key,
|
||||
machine_name : machine_name,
|
||||
reverse_interact : reverse_interact,
|
||||
info_node : info_node,
|
||||
version_management : {
|
||||
active : version_management_active,
|
||||
password : version_management_password
|
||||
}
|
||||
};
|
||||
fs.writeFileSync(conf.INTERACTION_CONF, json5.stringify(new_interaction_conf, null, 4));
|
||||
} catch(e) {
|
||||
console.error('Error when writting configuration file %s', conf.INTERACTION_CONF);
|
||||
return cb(e);
|
||||
}
|
||||
|
||||
// Don't block the event loop
|
||||
process.nextTick(function() {
|
||||
cb(null, new_interaction_conf);
|
||||
});
|
||||
};
|
||||
|
||||
InteractorDaemonizer.disconnectRPC = function(cb) {
|
||||
if (!InteractorDaemonizer.client_sock ||
|
||||
!InteractorDaemonizer.client_sock.close)
|
||||
return cb(null, {
|
||||
success : false,
|
||||
msg : 'RPC connection to Interactor Daemon is not launched'
|
||||
});
|
||||
|
||||
if (InteractorDaemonizer.client_sock.connected === false ||
|
||||
InteractorDaemonizer.client_sock.closing === true) {
|
||||
return cb(null, {
|
||||
success : false,
|
||||
msg : 'RPC closed'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
var timer;
|
||||
|
||||
debug('Closing RPC INTERACTOR');
|
||||
|
||||
InteractorDaemonizer.client_sock.once('close', function() {
|
||||
debug('RPC INTERACTOR cleanly closed');
|
||||
clearTimeout(timer);
|
||||
return cb ? cb(null, {success:true}) : false;
|
||||
});
|
||||
|
||||
timer = setTimeout(function() {
|
||||
if (InteractorDaemonizer.client_sock.destroy)
|
||||
InteractorDaemonizer.client_sock.destroy();
|
||||
return cb ? cb(null, {success:true}) : false;
|
||||
}, 200);
|
||||
|
||||
InteractorDaemonizer.client_sock.close();
|
||||
} catch(e) {
|
||||
debug('Error while closing RPC INTERACTOR', e.stack || e);
|
||||
return cb ? cb(e.stack || e) : false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
InteractorDaemonizer.rescueStart = function(conf, cb) {
|
||||
InteractorDaemonizer.getOrSetConf(conf, null, function(err, infos) {
|
||||
if (err || !infos) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('[Keymetrics.io]') + ' Using (Public key: %s) (Private key: %s)', infos.public_key, infos.secret_key);
|
||||
|
||||
daemonize(conf, infos, function(err, msg, proc) {
|
||||
return cb(err, msg, proc);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
InteractorDaemonizer.launchAndInteract = function(conf, opts, cb) {
|
||||
// For Watchdog
|
||||
if (process.env.PM2_AGENT_ONLINE) {
|
||||
return process.nextTick(cb);
|
||||
}
|
||||
|
||||
process.env.PM2_INTERACTOR_PROCESSING = true;
|
||||
|
||||
this.getOrSetConf(conf, opts, function(err, data) {
|
||||
if (err || !data) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
//console.log(chalk.cyan('[Keymetrics.io]') + ' Using (Public key: %s) (Private key: %s)', data.public_key, data.secret_key);
|
||||
|
||||
launchOrAttach(conf, data, function(err, msg, proc) {
|
||||
if (err)
|
||||
return cb(err);
|
||||
return cb(null, msg, proc);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @method getInteractInfo
|
||||
* @param {} cb
|
||||
* @return
|
||||
*/
|
||||
InteractorDaemonizer.getInteractInfo = function(conf, cb) {
|
||||
debug('Getting interaction info');
|
||||
if (process.env.PM2_NO_INTERACTION) return;
|
||||
InteractorDaemonizer.ping(conf, function(online) {
|
||||
if (!online) {
|
||||
return cb(new Error('Interactor is offline'));
|
||||
}
|
||||
InteractorDaemonizer.launchRPC(conf, function() {
|
||||
InteractorDaemonizer.rpc.getInfos(function(err, infos) {
|
||||
if (err)
|
||||
return cb(err);
|
||||
|
||||
// Avoid general CLI to interfere with Keymetrics CLI commands
|
||||
if (process.env.PM2_INTERACTOR_PROCESSING)
|
||||
return cb(null, infos);
|
||||
|
||||
InteractorDaemonizer.disconnectRPC(function() {
|
||||
return cb(null, infos);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
return false;
|
||||
});
|
||||
};
|
||||
@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
var crypto = require('crypto');
|
||||
|
||||
var saltChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
var saltCharsCount = saltChars.length;
|
||||
|
||||
function generateSalt(len) {
|
||||
if (typeof len != 'number' || len <= 0 || len !== parseInt(len, 10)) throw new Error('Invalid salt length');
|
||||
if (crypto.randomBytes) {
|
||||
return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').substring(0, len);
|
||||
} else {
|
||||
for (var i = 0, salt = ''; i < len; i++) {
|
||||
salt += saltChars.charAt(Math.floor(Math.random() * saltCharsCount));
|
||||
}
|
||||
return salt;
|
||||
}
|
||||
}
|
||||
|
||||
function generateHash(algorithm, salt, password, iterations) {
|
||||
iterations = iterations || 1;
|
||||
try {
|
||||
var hash = password;
|
||||
for(var i=0; i<iterations; ++i) {
|
||||
hash = crypto.createHmac(algorithm, salt).update(hash).digest('hex');
|
||||
}
|
||||
|
||||
return algorithm + '$' + salt + '$' + iterations + '$' + hash;
|
||||
} catch (e) {
|
||||
throw new Error('Invalid message digest algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
function makeBackwardCompatible(hashedPassword) {
|
||||
var parts = hashedPassword.split('$');
|
||||
if(parts.length === 3) {
|
||||
parts.splice(2,0,1);
|
||||
hashedPassword = parts.join("$");
|
||||
}
|
||||
|
||||
return hashedPassword;
|
||||
}
|
||||
|
||||
module.exports.generate = function(password, options) {
|
||||
if (typeof password != 'string') throw new Error('Invalid password');
|
||||
options || (options = {});
|
||||
options.algorithm || (options.algorithm = 'sha1');
|
||||
options.saltLength || options.saltLength == 0 || (options.saltLength = 8);
|
||||
options.iterations || (options.iterations = 1);
|
||||
var salt = generateSalt(options.saltLength);
|
||||
return generateHash(options.algorithm, salt, password, options.iterations);
|
||||
};
|
||||
|
||||
module.exports.verify = function(password, hashedPassword) {
|
||||
if (!password || !hashedPassword) return false;
|
||||
hashedPassword = makeBackwardCompatible(hashedPassword);
|
||||
var parts = hashedPassword.split('$');
|
||||
if (parts.length != 4) return false;
|
||||
try {
|
||||
return generateHash(parts[0], parts[1], password, parts[2]) == hashedPassword;
|
||||
} catch (e) {}
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports.isHashed = function(password) {
|
||||
if (!password) return false;
|
||||
return password.split('$').length == 4;
|
||||
}
|
||||
@ -1,380 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var axon = require('pm2-axon');
|
||||
var os = require('os');
|
||||
var debug = require('debug')('interface:push-interactor');
|
||||
var debugInfo = require('debug')('interface:push:delay');
|
||||
var util = require('util');
|
||||
var Url = require('url');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var pkg = require('../../package.json');
|
||||
var cst = require('../../constants.js');
|
||||
var Filter = require('./Filter.js');
|
||||
var Cipher = require('./Cipher.js');
|
||||
var Utility = require('../Utility.js');
|
||||
var InteractorUtility = require('./Utility.js');
|
||||
var Aggregator = require('./TransactionAggregator.js');
|
||||
|
||||
var LOGS_BUFFER = {};
|
||||
|
||||
/**
|
||||
* Instanciate a new axon connection
|
||||
*/
|
||||
function setupConnection(host, port) {
|
||||
var that = this;
|
||||
|
||||
this._setup = function(host, port) {
|
||||
console.log('[PUSH] Connecting %s:%s', host, port || cst.REMOTE_PORT_TCP);
|
||||
|
||||
var client = this.client = axon.socket('pub');
|
||||
if (port) port = parseInt(port)
|
||||
if (port === 41624) port = 80
|
||||
|
||||
this.host = host;
|
||||
|
||||
client.on('connect', function() {
|
||||
console.log('[PUSH] Connected');
|
||||
});
|
||||
|
||||
client.on('error', function(e) {
|
||||
console.log('[PUSH] Client got error', e.message);
|
||||
});
|
||||
|
||||
client.on('close', function(e) {
|
||||
console.log('[PUSH] Connection closed');
|
||||
});
|
||||
|
||||
client.on('reconnect attempt', function(e) {
|
||||
console.log('[PUSH] Reconnecting');
|
||||
});
|
||||
client.connect(port || cst.REMOTE_PORT_TCP, host);
|
||||
};
|
||||
|
||||
this.destroy = function() {
|
||||
this.client.close();
|
||||
this.client.removeAllListeners();
|
||||
};
|
||||
|
||||
this.reconnect = function() {
|
||||
this.destroy();
|
||||
this._setup(this.host);
|
||||
};
|
||||
|
||||
this._setup(host, port);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
var PushInteractor = module.exports = {
|
||||
/**
|
||||
* Connect to target host or reconnect if null is passed
|
||||
* the host param must be formated like (http://HOST:PORT)
|
||||
*/
|
||||
connectRemote: function (hostname, port) {
|
||||
if (this.socket) this.socket.destroy()
|
||||
if (hostname) {
|
||||
var parsed = Url.parse(hostname)
|
||||
this.socket = setupConnection(parsed.hostname, port || parsed.port)
|
||||
} else if (this.socket && this.socket.host) {
|
||||
this.socket = setupConnection(this.socket.host)
|
||||
} else {
|
||||
return console.error('NO HOST DEFINED')
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Start the PushInteractor Singleton
|
||||
*/
|
||||
start : function(p) {
|
||||
if (!p.url)
|
||||
throw new Error('missing endpoint url');
|
||||
if (!p.conf || !p.conf.ipm2)
|
||||
throw new Error('ipm2 is not initialized');
|
||||
|
||||
var self = this;
|
||||
|
||||
this.monitored_processes = {};
|
||||
this.conf = p.conf;
|
||||
this.ipm2 = p.conf.ipm2;
|
||||
this.send_buffer = [];
|
||||
this._reconnect_counter = 0;
|
||||
|
||||
this.port = null
|
||||
if (process.env.PM2_DEBUG)
|
||||
this.port = 3900;
|
||||
if (process.env.NODE_ENV == 'local_test')
|
||||
this.port = 8080;
|
||||
|
||||
this.resetPacket();
|
||||
|
||||
this.connectRemote(p.url, this.port);
|
||||
|
||||
this.ipm2.on('ready', function() {
|
||||
console.log('[PUSH] PM2 interface ready, listening to PM2');
|
||||
self.listenToPM2Events();
|
||||
});
|
||||
|
||||
self.startPoolingWorker();
|
||||
self.cache = new InteractorUtility.Cache({
|
||||
miss: function (key) {
|
||||
try {
|
||||
var content = fs.readFileSync(path.resolve(key));
|
||||
return content.toString().split(/\r?\n/);
|
||||
} catch (err) {
|
||||
debug('Error while trying to get file from FS : %s', err.message || err)
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
ttl: 60 * 30
|
||||
});
|
||||
self.stackParser = new InteractorUtility.StackTraceParser({ cache: self.cache, context: cst.CONTEXT_ON_ERROR });
|
||||
self.aggregator = new Aggregator(self);
|
||||
self.aggregator.init();
|
||||
},
|
||||
/**
|
||||
* Send bufferized data at regular interval
|
||||
*/
|
||||
startPoolingWorker : function() {
|
||||
var self = this;
|
||||
|
||||
setInterval(function() {
|
||||
debug('[PUSH] +---- Pooling: sending data ----+');
|
||||
PushInteractor.sendData();
|
||||
}, cst.SEND_INTERVAL);
|
||||
},
|
||||
/**
|
||||
* Send profiling file asynchronously
|
||||
*/
|
||||
sendFile : function(packet) {
|
||||
var self = this;
|
||||
var file = JSON.parse(JSON.stringify(packet.data.return.dump_file));
|
||||
|
||||
var meta = {
|
||||
pm_id : packet.process.pm_id,
|
||||
name : packet.process.name,
|
||||
server_name : PushInteractor.conf.MACHINE_NAME,
|
||||
public_key : self.conf.PUBLIC_KEY
|
||||
};
|
||||
|
||||
if (packet.data.return.heapdump === true)
|
||||
meta.heapdump = true;
|
||||
if (packet.data.return.cpuprofile === true)
|
||||
meta.cpuprofile = true;
|
||||
|
||||
fs.readFile(file, function(err, data) {
|
||||
if (err) return console.error(err.stack || err);
|
||||
fs.unlink(file, function(e) { if (e) console.error(e.stack || e);});
|
||||
return self.socket.client.send(JSON.stringify(meta), data);
|
||||
});
|
||||
},
|
||||
listenToPM2Events : function() {
|
||||
var self = this;
|
||||
|
||||
this.ipm2.bus.on('*', function(event, packet) {
|
||||
if (event == 'axm:action') return false;
|
||||
|
||||
// Drop transitional state processes (_old_*)
|
||||
if (packet &&
|
||||
packet.process &&
|
||||
packet.process.pm_id &&
|
||||
(typeof(packet.process.pm_id) == 'string' &&
|
||||
packet.process.pm_id.indexOf('_old') > -1))
|
||||
return false;
|
||||
|
||||
if (Object.keys(self.monitored_processes).length > 0 &&
|
||||
!self.monitored_processes[packet.process.pm_id])
|
||||
return false;
|
||||
|
||||
// keep log in a buffer
|
||||
if (event.match(/^log:/)) {
|
||||
if (!LOGS_BUFFER[packet.process.name]) {
|
||||
LOGS_BUFFER[packet.process.name] = [];
|
||||
}
|
||||
// push the log data
|
||||
LOGS_BUFFER[packet.process.name].push(packet.data);
|
||||
// delete the last one if too long
|
||||
if (LOGS_BUFFER[packet.process.name].length >= cst.LOGS_BUFFER_SIZE) {
|
||||
LOGS_BUFFER[packet.process.name].pop();
|
||||
}
|
||||
|
||||
// don't send if not asked
|
||||
if (!global._logs) return false;
|
||||
}
|
||||
|
||||
// attach additional info on exception
|
||||
if (event === 'process:exception') {
|
||||
packet.data.last_logs = LOGS_BUFFER[packet.process.name];
|
||||
packet.data = self.stackParser.attachContext(packet.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a heapdump action
|
||||
*/
|
||||
if (event == 'axm:reply' && packet.data && packet.data.return && (packet.data.return.heapdump || packet.data.return.cpuprofile)) {
|
||||
PushInteractor.sendFile(packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event == 'human:event') {
|
||||
packet.name = packet.data.__name + '';
|
||||
delete packet.data.__name;
|
||||
}
|
||||
|
||||
if (!packet.process)
|
||||
return console.error('No process field [%s]', event);
|
||||
|
||||
/**
|
||||
* Process specific messages
|
||||
* -- Reformat raw output of pm2-interface
|
||||
*/
|
||||
packet.process = {
|
||||
pm_id : packet.process.pm_id,
|
||||
name : packet.process.name,
|
||||
rev : packet.process.rev || ((packet.process.versioning && packet.process.versioning.revision) ? packet.process.versioning.revision : null),
|
||||
server: PushInteractor.conf.MACHINE_NAME
|
||||
};
|
||||
|
||||
// agregate transaction data before sending them
|
||||
if (event.indexOf('axm:trace') > -1)
|
||||
return self.aggregator.aggregate(packet);
|
||||
|
||||
if (event.match(/^log:/)) {
|
||||
packet.log_type = event.split(':')[1];
|
||||
event = 'logs';
|
||||
}
|
||||
return PushInteractor.bufferData(event, packet);
|
||||
});
|
||||
},
|
||||
resetPacket : function() {
|
||||
var self = this;
|
||||
|
||||
this._packet = {
|
||||
'server_name' : self.conf.MACHINE_NAME,
|
||||
'status' : {},
|
||||
'monitoring' : {}
|
||||
};
|
||||
},
|
||||
bufferData : function(event, packet) {
|
||||
var self = this;
|
||||
var logs_limit_size = 1024 * 50;
|
||||
|
||||
// if (Object.keys(self._packet).indexOf(event) == -1) {
|
||||
// return console.error('SKIP unknown field name [%s]', event);
|
||||
// }
|
||||
debug('Buffering one more event %s', event);
|
||||
|
||||
if (!(event in self._packet))
|
||||
self._packet[event] = [];
|
||||
|
||||
if (packet.process && !packet.server) {
|
||||
if (event === 'logs'
|
||||
&& (JSON.stringify(self._packet[event]).length > logs_limit_size
|
||||
|| self._packet[event].length > 100))
|
||||
return console.error('Logs packet larger than 50KB limit');
|
||||
|
||||
self._packet[event].push(packet);
|
||||
}
|
||||
else {
|
||||
console.error('Got packet without any process');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
preparePacket : function(cb) {
|
||||
var self = this;
|
||||
|
||||
this.ipm2.rpc.getMonitorData({}, function(err, processes) {
|
||||
if (!processes)
|
||||
return console.error('Cant access to getMonitorData RPC PM2 method');
|
||||
|
||||
processes = processes.filter(function (proc) {
|
||||
return proc.pm2_env._km_monitored !== false;
|
||||
});
|
||||
|
||||
var ret = null;
|
||||
|
||||
if ((ret = Filter.monitoring(processes, PushInteractor.conf))) {
|
||||
self._packet['monitoring'] = ret;
|
||||
}
|
||||
|
||||
if ((ret = Filter.machineSnapshot(processes, PushInteractor.conf))) {
|
||||
self._packet['status'] = {
|
||||
data : ret,
|
||||
server_name : self.conf.MACHINE_NAME,
|
||||
internal_ip : self.conf.internal_ip,
|
||||
protected : global._pm2_password_protected,
|
||||
rev_con : self.conf.rev_con
|
||||
};
|
||||
}
|
||||
|
||||
return cb ? cb(null, ret) : false;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Description
|
||||
* @method send_data
|
||||
* @return
|
||||
*/
|
||||
sendData : function() {
|
||||
var self = this;
|
||||
|
||||
if (self.socket.client &&
|
||||
self.socket.client.socks[0] &&
|
||||
self.socket.client.socks[0].bufferSize > 290000) {
|
||||
self.resetPacket();
|
||||
self._reconnect_counter++;
|
||||
console.log('Buffer size too high (%d), stopping buffering and sending', self.socket.client.socks[0].bufferSize);
|
||||
|
||||
if (self._reconnect_counter > 20) {
|
||||
console.log('[PUSH] Forcing reconnection');
|
||||
self._reconnect_counter = 0;
|
||||
self.socket.reconnect();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.preparePacket(function() {
|
||||
var data = {};
|
||||
|
||||
if (process.env.NODE_ENV &&
|
||||
(process.env.NODE_ENV == 'test' || process.env.NODE_ENV == 'local_test')) {
|
||||
data = {
|
||||
public_key : PushInteractor.conf.PUBLIC_KEY,
|
||||
sent_at : Utility.getDate(),
|
||||
data : self._packet
|
||||
};
|
||||
}
|
||||
else {
|
||||
var cipheredData = Cipher.cipherMessage(JSON.stringify(self._packet),
|
||||
PushInteractor.conf.SECRET_KEY);
|
||||
data = {
|
||||
public_key : self.conf.PUBLIC_KEY,
|
||||
sent_at : Utility.getDate(),
|
||||
data : cipheredData
|
||||
};
|
||||
}
|
||||
|
||||
var str = JSON.stringify(data);
|
||||
var t1 = new Date();
|
||||
|
||||
self.resetPacket();
|
||||
|
||||
if (!self.socket) return false;
|
||||
|
||||
self.socket.client.sendv2(str, function() {
|
||||
var duration_sec = (new Date() - t1) / 1000;
|
||||
debugInfo('Time to flush data %ds (buffer size %d)', duration_sec);
|
||||
|
||||
if (duration_sec > 1)
|
||||
console.info('[WARN] Time to send data over TCP took %dseconds!', duration_sec);
|
||||
|
||||
data = null;
|
||||
str = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var debug = require('debug')('interface:driver');
|
||||
var Cipher = require('../Cipher.js');
|
||||
|
||||
var CustomActions = module.exports = {
|
||||
/**
|
||||
* Method to trigger custom actions (axm actions)
|
||||
*/
|
||||
axmCustomActions : function() {
|
||||
var self = this;
|
||||
|
||||
this.socket.data('trigger:action', function(raw_msg) {
|
||||
var msg = {};
|
||||
|
||||
if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' ||
|
||||
process.env.NODE_ENV == 'local_test'))
|
||||
msg = raw_msg;
|
||||
else
|
||||
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
|
||||
|
||||
if (!msg) return console.error('Error while receiving message! #axmCustomActions');
|
||||
|
||||
console.log('New remote action %s triggered for process %s', msg.action_name, msg.process_id);
|
||||
self.pm2_instance.msgProcess({
|
||||
id : msg.process_id,
|
||||
msg : msg.action_name,
|
||||
opts: msg.opts || null
|
||||
}, function(err, data) {
|
||||
if (err) {
|
||||
return self.socket.send('trigger:action:failure', {
|
||||
success : false,
|
||||
err : err.message,
|
||||
id : msg.process_id,
|
||||
action_name : msg.action_name
|
||||
});
|
||||
}
|
||||
console.log('[REVERSE INTERACTOR] Message received from AXM for proc_id : %s and action name %s',
|
||||
msg.process_id, msg.action_name);
|
||||
|
||||
return self.socket.send('trigger:action:success', {
|
||||
success : true,
|
||||
id : msg.process_id,
|
||||
action_name : msg.action_name
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.socket.data('trigger:scoped_action', function(raw_msg) {
|
||||
var msg = {};
|
||||
|
||||
if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' ||
|
||||
process.env.NODE_ENV == 'local_test'))
|
||||
msg = raw_msg;
|
||||
else
|
||||
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
|
||||
|
||||
if (!msg) return console.error('Error while receiving message! #axmCustomActions');
|
||||
|
||||
console.log('New SCOPED action %s triggered for process %s', msg.action_name, msg.process.pm_id);
|
||||
|
||||
self.pm2_instance.msgProcess({
|
||||
id : msg.process.pm_id,
|
||||
action_name : msg.action_name,
|
||||
msg : msg.action_name,
|
||||
opts : msg.options || {},
|
||||
uuid : msg.uuid
|
||||
}, function(err, data) {
|
||||
if (err) {
|
||||
return self.socket.send('trigger:action:failure', {
|
||||
success : false,
|
||||
err : err.message,
|
||||
id : msg.process.pm_id,
|
||||
action_name : msg.action_name
|
||||
});
|
||||
}
|
||||
console.log('[REVERSE INTERACTOR] Message received from AXM for proc_id : %s and action name %s',
|
||||
msg.process_id, msg.action_name);
|
||||
|
||||
return self.socket.send('trigger:action:success', {
|
||||
success : true,
|
||||
id : msg.process.pm_id,
|
||||
action_name : msg.action_name
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,345 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var debug = require('debug')('interface:driver');
|
||||
var Url = require('url');
|
||||
var Cipher = require('../Cipher.js');
|
||||
var PushInteractor = require('../PushInteractor');
|
||||
var Conf = require('../../Configuration.js');
|
||||
var Password = require('../Password.js');
|
||||
|
||||
/**
|
||||
* Allowed remote PM2 methods
|
||||
* with options
|
||||
* - password_required : force to pass a password in parameter
|
||||
* - password_optional : if a password is set, force it
|
||||
* - lock : enable the locking system (block parallel commands)
|
||||
*/
|
||||
var PM2_REMOTE_METHOD_ALLOWED = {
|
||||
'restart' : {},
|
||||
'reload' : {},
|
||||
'gracefulReload' : {},
|
||||
'reset' : {},
|
||||
'scale' : {},
|
||||
|
||||
'install' : { password_required : true },
|
||||
'uninstall' : { password_required : true },
|
||||
'stop' : { password_required : true },
|
||||
'delete' : { password_required : true },
|
||||
'set' : {},
|
||||
'multiset' : {},
|
||||
'deepUpdate' : { password_required : true },
|
||||
|
||||
'pullAndRestart' : { password_optional : true },
|
||||
'forward' : { password_optional : true },
|
||||
'backward' : { password_optional : true },
|
||||
|
||||
'startLogging' : {},
|
||||
'stopLogging' : {},
|
||||
|
||||
'resetTransactionCache': {},
|
||||
'resetFileCache': {},
|
||||
|
||||
// This is just for testing purproses
|
||||
'ping' : { password_required : true }
|
||||
};
|
||||
|
||||
var Pm2Actions = module.exports = {
|
||||
/**
|
||||
* Methods to trigger PM2 actions from remote
|
||||
*/
|
||||
pm2Actions : function() {
|
||||
var self = this;
|
||||
|
||||
function executionBox(msg, cb) {
|
||||
/**
|
||||
* Exemple
|
||||
* msg = {
|
||||
* method_name : 'restart',
|
||||
* parameters : {}
|
||||
* }
|
||||
*/
|
||||
console.log('PM2 action from remote triggered "pm2 %s %j"',
|
||||
msg.method_name,
|
||||
msg.parameters);
|
||||
|
||||
var method_name = JSON.parse(JSON.stringify(msg.method_name));
|
||||
|
||||
var parameters = '';
|
||||
|
||||
try {
|
||||
parameters = JSON.parse(JSON.stringify(msg.parameters));
|
||||
}
|
||||
catch(e) {
|
||||
console.error(e.stack || e);
|
||||
parameters = msg.parameters;
|
||||
}
|
||||
|
||||
if (!method_name) {
|
||||
console.error('no method name');
|
||||
return cb(new Error('no method name defined'));
|
||||
}
|
||||
|
||||
if (!PM2_REMOTE_METHOD_ALLOWED[method_name]) {
|
||||
console.error('method %s not allowed', method_name);
|
||||
return cb(new Error('method ' + method_name + ' not allowed'));
|
||||
}
|
||||
|
||||
if (method_name === 'startLogging') {
|
||||
global._logs = true;
|
||||
// Stop streaming logs automatically after timeout
|
||||
setTimeout(function() {
|
||||
global._logs = false;
|
||||
}, 120000);
|
||||
return cb(null, 'Log streaming enabled');
|
||||
} else if (method_name === 'stopLogging') {
|
||||
global._logs = false;
|
||||
return cb(null, 'Log streaming disabled');
|
||||
} else if (method_name === 'resetTransactionCache') {
|
||||
PushInteractor.aggregator.clearData();
|
||||
return cb(null, 'Transaction cache has beem reset');
|
||||
} else if (method_name === 'resetFileCache') {
|
||||
PushInteractor.cache.reset();
|
||||
return cb(null, 'File cache has beem reset');
|
||||
}
|
||||
|
||||
self.pm2_instance.remote(method_name, parameters, cb);
|
||||
return false;
|
||||
}
|
||||
|
||||
function sendBackResult(data) {
|
||||
self.socket.send('trigger:pm2:result', data);
|
||||
};
|
||||
|
||||
this.socket.data('trigger:pm2:action', function(raw_msg) {
|
||||
var d = require('domain').create();
|
||||
|
||||
var msg = {};
|
||||
|
||||
/**
|
||||
* Uncipher Data
|
||||
*/
|
||||
if (process.env.NODE_ENV &&
|
||||
(process.env.NODE_ENV == 'test' ||
|
||||
process.env.NODE_ENV == 'local_test'))
|
||||
msg = raw_msg;
|
||||
else
|
||||
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
|
||||
|
||||
d.on('error', function(e) {
|
||||
console.error('Error caught in domain');
|
||||
console.error(e.stack || e);
|
||||
|
||||
/**
|
||||
* Send error back to
|
||||
*/
|
||||
sendBackResult({
|
||||
ret : {
|
||||
err : e,
|
||||
data : null
|
||||
},
|
||||
meta : {
|
||||
method_name : msg.method_name,
|
||||
app_name : msg.parameters.name,
|
||||
machine_name : self.conf.MACHINE_NAME,
|
||||
public_key : self.conf.PUBLIC_KEY
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
d.run(function() {
|
||||
if (!msg)
|
||||
throw new Error('Wrong SECRET KEY to uncipher package');
|
||||
|
||||
/**
|
||||
* Execute command
|
||||
*/
|
||||
executionBox(msg, function(err, data) {
|
||||
if (err) console.error(err.stack || JSON.stringify(err));
|
||||
|
||||
/**
|
||||
* Send back the result
|
||||
*/
|
||||
sendBackResult({
|
||||
ret : {
|
||||
err : err,
|
||||
data : data || null
|
||||
},
|
||||
meta : {
|
||||
method_name : msg.method_name,
|
||||
app_name : msg.parameters.name,
|
||||
machine_name : self.conf.MACHINE_NAME,
|
||||
public_key : self.conf.PUBLIC_KEY
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
/****************************************************
|
||||
*
|
||||
*
|
||||
* Scoped PM2 Actions with streaming and multi args
|
||||
*
|
||||
*
|
||||
****************************************************/
|
||||
pm2ScopedActions : function() {
|
||||
var self = this;
|
||||
|
||||
this.socket.data('trigger:pm2:scoped:action', function(raw_msg) {
|
||||
var msg = {};
|
||||
|
||||
if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' ||
|
||||
process.env.NODE_ENV == 'local_test'))
|
||||
msg = raw_msg;
|
||||
else {
|
||||
/**
|
||||
* Uncipher Data
|
||||
*/
|
||||
msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY);
|
||||
}
|
||||
|
||||
if (!msg.uuid ||
|
||||
!msg.action_name) {
|
||||
console.error('PM2 Scoped: Parameter missing!');
|
||||
return sendEvent('pm2:scoped:error', {
|
||||
at : Date.now(),
|
||||
out : 'Parameter missing',
|
||||
msg : msg.uuid || null
|
||||
});
|
||||
}
|
||||
|
||||
sendEvent('pm2:scoped:stream', {
|
||||
at : Date.now(),
|
||||
out : 'Action ' + msg.action_name + ' received',
|
||||
uuid : msg.uuid
|
||||
});
|
||||
|
||||
executionBox(msg, function(err, data) {
|
||||
if (err) {
|
||||
console.error(err.stack || err);
|
||||
return sendEvent('pm2:scoped:error', {
|
||||
at : Date.now(),
|
||||
out : err.stack || err,
|
||||
uuid : msg.uuid
|
||||
});
|
||||
}
|
||||
return sendEvent('pm2:scoped:end', {
|
||||
at : Date.now(),
|
||||
out : data,
|
||||
uuid : msg.uuid
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Compact event in Push Interactor *pipe*
|
||||
*/
|
||||
function sendEvent(event, data) {
|
||||
var packet = {
|
||||
at : Date.now(),
|
||||
data : {
|
||||
data : data.out,
|
||||
uuid : data.uuid
|
||||
}
|
||||
};
|
||||
|
||||
if (!PushInteractor._packet[event])
|
||||
PushInteractor._packet[event] = [];
|
||||
|
||||
PushInteractor._packet[event].push(packet);
|
||||
|
||||
if (process.env.NODE_ENV == 'local_test')
|
||||
process.send({event : event, data : data});
|
||||
};
|
||||
|
||||
/**
|
||||
* Processing
|
||||
*/
|
||||
function executionBox(msg, cb) {
|
||||
var action_name = msg.action_name;
|
||||
var opts = msg.options;
|
||||
|
||||
if (!PM2_REMOTE_METHOD_ALLOWED[action_name]) {
|
||||
console.error('method %s not allowed', action_name);
|
||||
return cb(new Error('method ' + action_name + ' not allowed'));
|
||||
}
|
||||
|
||||
var action_conf = PM2_REMOTE_METHOD_ALLOWED[action_name];
|
||||
|
||||
/**
|
||||
* Password checking
|
||||
*/
|
||||
if (action_conf.password_required === true) {
|
||||
if (!msg.password) {
|
||||
console.error('Missing password in query');
|
||||
return cb('Missing password in query');
|
||||
}
|
||||
|
||||
var passwd = Conf.getSync('pm2:passwd');
|
||||
|
||||
if (passwd === null) {
|
||||
console.error('Password at PM2 level is missing');
|
||||
return cb('Password at PM2 level is missing please set password via pm2 set pm2:passwd <password>');
|
||||
}
|
||||
|
||||
if (Password.verify(msg.password, passwd) != true) {
|
||||
console.error('Password does not match');
|
||||
return cb('Password does not match');
|
||||
}
|
||||
}
|
||||
|
||||
if (action_conf.lock === false)
|
||||
opts.lock = false;
|
||||
|
||||
/**
|
||||
* Fork the remote action in another process
|
||||
* so we can catch the stdout/stderr and emit it
|
||||
*/
|
||||
var fork = require('child_process').fork;
|
||||
|
||||
process.env.fork_params = JSON.stringify({ action : action_name, opts : opts});
|
||||
|
||||
console.log('Executing: pm2 %s %s', action_name, opts.args ? opts.args.join(' ') : '');
|
||||
|
||||
var app = fork(__dirname + '/ScopedExecution.js', [], {
|
||||
silent : true
|
||||
});
|
||||
|
||||
app.stdout.on('data', function(dt) {
|
||||
console.log(dt.toString());
|
||||
sendEvent('pm2:scoped:stream', {
|
||||
at : Date.now(),
|
||||
out : dt.toString(),
|
||||
uuid : msg.uuid
|
||||
});
|
||||
});
|
||||
|
||||
app.once('error', function(dt) {
|
||||
console.error('Error got?', dt);
|
||||
sendEvent('pm2:scoped:error', {
|
||||
at : Date.now(),
|
||||
out : 'Shit happening ' + JSON.stringify(dt),
|
||||
msg : msg.uuid
|
||||
});
|
||||
});
|
||||
|
||||
app.on('message', function(dt) {
|
||||
var ret = JSON.parse(dt);
|
||||
if (ret.isFinished != true) return false;
|
||||
|
||||
console.log('Action %s finished (err= %s)',
|
||||
action_name, ret.err);
|
||||
return cb(ret.err, ret.dt);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var pm2 = require('../../..');
|
||||
var domain = require('domain');
|
||||
var Utility = require('../../Utility.js');
|
||||
|
||||
var d = domain.create();
|
||||
|
||||
d.once('error', function(err) {
|
||||
process.send(JSON.stringify({err: err.stack, isFinished : true}));
|
||||
});
|
||||
|
||||
d.run(function() {
|
||||
var params = JSON.parse(process.env.fork_params);
|
||||
|
||||
console.log('Executing: pm2 %s %s',
|
||||
params.action,
|
||||
params.opts.args ? params.opts.args.join(' ') : '');
|
||||
|
||||
pm2.connect(function() {
|
||||
pm2.remoteV2(params.action, params.opts, function(err, dt) {
|
||||
process.send(JSON.stringify(Utility.clone({
|
||||
err: err,
|
||||
dt: dt,
|
||||
isFinished : true
|
||||
})));
|
||||
pm2.disconnect(process.exit);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,128 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var debug = require('debug')('interface:driver');
|
||||
var nssocket = require('nssocket');
|
||||
var Url = require('url');
|
||||
var Cipher = require('./Cipher.js');
|
||||
var util = require('util');
|
||||
|
||||
var ReverseInteract = {
|
||||
changeUrl : function(url) {
|
||||
if (!this.connected) return;
|
||||
console.log('[REV] Changing URL to %s', url);
|
||||
|
||||
this.network = Url.parse(url);
|
||||
this.socket.connect(parseInt(this.network.port), this.network.hostname);
|
||||
this.socket.reconnect();
|
||||
},
|
||||
destroy : function() {
|
||||
this.socket.destroy();
|
||||
},
|
||||
reconnect : function() {
|
||||
console.log('[REV] Reconnecting to %s', this.network.hostname);
|
||||
this.socket.reconnect();
|
||||
},
|
||||
start : function(opts) {
|
||||
var self = this;
|
||||
|
||||
if (!opts.url)
|
||||
throw new Error('url not declared');
|
||||
if (!opts.conf)
|
||||
throw new Error('Conf not passed to ReverseInteractor');
|
||||
|
||||
this.connected = false;
|
||||
this.conf = opts.conf;
|
||||
this.network = Url.parse(opts.url);
|
||||
this.pm2_instance = opts.conf.pm2_instance;
|
||||
|
||||
this.socket = new nssocket.NsSocket({
|
||||
type : 'tcp4',
|
||||
reconnect : true,
|
||||
retryInterval : 2000,
|
||||
max : Infinity,
|
||||
maxListeners : 50
|
||||
});
|
||||
|
||||
this.socket.on('error', function(e) {
|
||||
self.connected = false;
|
||||
console.error('[REV] %s', e.message || e);
|
||||
});
|
||||
|
||||
this.socket.on('close', function(dt) {
|
||||
self.connected = false;
|
||||
});
|
||||
|
||||
this.socket.on('start', function() {
|
||||
self.connected = true;
|
||||
opts.conf.rev_con = true;
|
||||
console.log('[REV] Connected to %s:%s', self.network.hostname, self.network.port);
|
||||
});
|
||||
|
||||
console.log('[REV] Connecting to %s:%s', this.network.hostname, this.network.port);
|
||||
|
||||
this.socket.connect(parseInt(this.network.port), this.network.hostname);
|
||||
this.onMessage();
|
||||
},
|
||||
/**
|
||||
* Listening to remote events from Keymetrics
|
||||
*/
|
||||
onMessage : function() {
|
||||
if (!this.socket) return console.error('Reverse interaction not initialized');
|
||||
|
||||
/**
|
||||
* Identify this agent to Keymetrics
|
||||
* via PUBLIC/PRIVATE key exchange
|
||||
*/
|
||||
ReverseInteract.introduceToKeymetrics();
|
||||
|
||||
ReverseInteract.axmCustomActions();
|
||||
|
||||
/**
|
||||
* From Pm2Actions.js
|
||||
*/
|
||||
ReverseInteract.pm2Actions();
|
||||
|
||||
ReverseInteract.pm2ScopedActions();
|
||||
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* First method called to identify this agent
|
||||
*/
|
||||
introduceToKeymetrics : function() {
|
||||
var self = this;
|
||||
|
||||
this.socket.data('ask', function(raw_msg) {
|
||||
if (process.env.NODE_ENV && process.env.NODE_ENV == 'test') {
|
||||
// Dont cipher data in test environment
|
||||
self.socket.send('ask:rep', {
|
||||
success : true,
|
||||
machine_name : self.conf.MACHINE_NAME,
|
||||
public_key : self.conf.PUBLIC_KEY
|
||||
});
|
||||
} else {
|
||||
var ciphered_data = Cipher.cipherMessage(JSON.stringify({
|
||||
machine_name : self.conf.MACHINE_NAME
|
||||
}), self.conf.SECRET_KEY);
|
||||
|
||||
if (!ciphered_data)
|
||||
return console.error('Got wrong ciphering data %s %s', self.conf.MACHINE_NAME, self.conf.SECRET_KEY);
|
||||
|
||||
self.socket.send('ask:rep', {
|
||||
data : ciphered_data,
|
||||
public_key : self.conf.PUBLIC_KEY
|
||||
});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
util._extend(ReverseInteract, require('./RemoteActions/Pm2Actions.js'));
|
||||
util._extend(ReverseInteract, require('./RemoteActions/CustomActions.js'));
|
||||
|
||||
module.exports = ReverseInteract;
|
||||
@ -1,684 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
var cst = require('../../constants.js');
|
||||
var log = require('debug')('pm2:aggregator');
|
||||
var async = require('async');
|
||||
var Utility = require('./Utility.js');
|
||||
var fclone = require('fclone');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Histogram = require('pmx/lib/utils/probes/Histogram.js');
|
||||
|
||||
var LABELS = {
|
||||
"HTTP_RESPONSE_CODE_LABEL_KEY": 'http/status_code',
|
||||
"HTTP_URL_LABEL_KEY": 'http/url',
|
||||
"HTTP_METHOD_LABEL_KEY": 'http/method',
|
||||
"HTTP_RESPONSE_SIZE_LABEL_KEY": 'http/response/size',
|
||||
"STACK_TRACE_DETAILS_KEY": 'stacktrace',
|
||||
"ERROR_DETAILS_NAME": 'error/name',
|
||||
"ERROR_DETAILS_MESSAGE": 'error/message',
|
||||
"HTTP_SOURCE_IP": 'http/source/ip',
|
||||
"HTTP_PATH_LABEL_KEY": "http/path"
|
||||
}
|
||||
var SPANS_DB = ['redis', 'mysql', 'pg', 'mongo', 'outbound_http'];
|
||||
|
||||
/**
|
||||
*
|
||||
* # Data structure sent to interactor
|
||||
*
|
||||
* {
|
||||
* 'process_name': {
|
||||
* process : {}, // PM2 process meta data
|
||||
* data : {
|
||||
* routes : [ // array of all routes ordered by count
|
||||
* {
|
||||
* path: '/', // path of the route
|
||||
* meta: {
|
||||
* count: 50, // count of this route
|
||||
* max: 300, // max latency of this route
|
||||
* min: 50, // min latency of this route
|
||||
* mean: 120 // mean latency of this route
|
||||
* }
|
||||
* variances: [{ // array of variance order by count
|
||||
* spans : [
|
||||
* ... // transactions
|
||||
* ],
|
||||
* count: 50, // count of this variance
|
||||
* max: 300, // max latency of this variance
|
||||
* min: 50, // min latency of this variance
|
||||
* mean: 120 // mean latency of this variance
|
||||
* }]
|
||||
* }
|
||||
* ],
|
||||
* meta : {
|
||||
* trace_count : 50, // trace number
|
||||
* mean_latency : 40, // global app latency in ms
|
||||
* http_meter : 30, // global app req per minutes
|
||||
* db_meter : 20, // number of database transaction per min
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
var TransactionAggregator = module.exports = function (pushInteractor) {
|
||||
if (!(this instanceof TransactionAggregator)) return new TransactionAggregator(pushInteractor);
|
||||
|
||||
var self = this;
|
||||
this.processes = {};
|
||||
this.stackParser = pushInteractor.stackParser;
|
||||
|
||||
/**
|
||||
* First method to call in real environment
|
||||
* - Listen to restart event for initialization period
|
||||
* - Clear aggregation on process stop
|
||||
* - Launch worker to attach data to be pushed to KM
|
||||
*/
|
||||
this.init = function () {
|
||||
// New Process Online, reset & wait a bit before processing
|
||||
pushInteractor.ipm2.bus.on('process:event', function (data) {
|
||||
if (data.event !== 'online' || !self.processes[data.process.name]) return false;
|
||||
|
||||
var rev = (data.process.versioning && data.process.versioning.revision)
|
||||
? data.process.versioning.revision : null;
|
||||
|
||||
self.resetAggregation(data.process.name, {
|
||||
rev: rev,
|
||||
server: pushInteractor.conf.MACHINE_NAME
|
||||
});
|
||||
});
|
||||
|
||||
// Process getting offline, delete aggregation
|
||||
pushInteractor.ipm2.bus.on('process:event', function (data) {
|
||||
if (data.event !== 'stop' || !self.processes[data.process.name]) return false;
|
||||
log('Deleting aggregation for %s', data.process.name);
|
||||
delete self.processes[data.process.name];
|
||||
});
|
||||
|
||||
self.launchWorker();
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset aggregation for target app_name
|
||||
*/
|
||||
this.resetAggregation = function (app_name, meta) {
|
||||
log('Reseting agg for app:%s meta:%j', app_name, meta);
|
||||
|
||||
if (self.processes[app_name].initialization_timeout) {
|
||||
log('Reseting initialization timeout app:%s', app_name);
|
||||
clearTimeout(self.processes[app_name].initialization_timeout);
|
||||
clearInterval(self.processes[app_name].notifier);
|
||||
self.processes[app_name].notifier = null;
|
||||
}
|
||||
|
||||
self.processes[app_name] = initializeRouteMeta({
|
||||
name: app_name,
|
||||
rev: meta.rev,
|
||||
server: meta.server
|
||||
});
|
||||
|
||||
var start = Date.now();
|
||||
self.processes[app_name].notifier = setInterval(function () {
|
||||
var elapsed = Date.now() - start;
|
||||
// failsafe
|
||||
if (elapsed / 1000 > cst.AGGREGATION_DURATION) {
|
||||
clearInterval(self.processes[app_name].notifier);
|
||||
self.processes[app_name].notifier = null;
|
||||
}
|
||||
|
||||
var msg = {
|
||||
data: {
|
||||
learning_duration: cst.AGGREGATION_DURATION,
|
||||
elapsed: elapsed
|
||||
},
|
||||
process: self.processes[app_name].process
|
||||
};
|
||||
pushInteractor && pushInteractor.bufferData('axm:transaction:learning', msg);
|
||||
}, 5000);
|
||||
|
||||
self.processes[app_name].initialization_timeout = setTimeout(function () {
|
||||
log('Initialization timeout finished for app:%s', app_name);
|
||||
clearInterval(self.processes[app_name].notifier);
|
||||
self.processes[app_name].notifier = null;
|
||||
self.processes[app_name].initialization_timeout = null;
|
||||
}, cst.AGGREGATION_DURATION);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear aggregated data for all process
|
||||
*/
|
||||
this.clearData = function () {
|
||||
var self = this;
|
||||
Object.keys(this.processes).forEach(function (process) {
|
||||
self.resetAggregation(process, self.processes[process].process);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate new entry for application
|
||||
*
|
||||
* @param {Object} process process meta
|
||||
*/
|
||||
function initializeRouteMeta (process) {
|
||||
if (process.pm_id) delete process.pm_id;
|
||||
|
||||
return {
|
||||
routes: {},
|
||||
meta: {
|
||||
trace_count: 0,
|
||||
http_meter: new Utility.EWMA(),
|
||||
db_meter: new Utility.EWMA(),
|
||||
histogram: new Histogram({ measurement: 'median' }),
|
||||
db_histograms: {}
|
||||
},
|
||||
process: process
|
||||
};
|
||||
}
|
||||
|
||||
this.getAggregation = function () {
|
||||
return this.processes;
|
||||
};
|
||||
|
||||
this.validateData = function (packet) {
|
||||
if (!packet || !packet.data) {
|
||||
log('Packet malformated', packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!packet.process) {
|
||||
log('Got packet without process: %j', packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!packet.data.spans || !packet.data.spans[0]) {
|
||||
log('Trace without spans: %s', Object.keys(packet.data));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!packet.data.spans[0].labels) {
|
||||
log('Trace spans without labels: %s', Object.keys(packet.data.spans));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to aggregate and compute stats for traces
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @param {Object} packet.process process metadata
|
||||
* @param {Object} packet.data trace
|
||||
*/
|
||||
this.aggregate = function(packet) {
|
||||
if (self.validateData(packet) === false) return false;
|
||||
|
||||
var new_trace = packet.data;
|
||||
var app_name = packet.process.name;
|
||||
|
||||
if (!self.processes[app_name]) {
|
||||
self.processes[app_name] = initializeRouteMeta(packet.process);
|
||||
}
|
||||
|
||||
var process = self.processes[app_name];
|
||||
|
||||
// Get http path of current span
|
||||
var path = new_trace.spans[0].labels[LABELS.HTTP_PATH_LABEL_KEY];
|
||||
|
||||
// Cleanup spans
|
||||
self.censorSpans(new_trace.spans);
|
||||
|
||||
// remove spans with startTime == endTime
|
||||
new_trace.spans = new_trace.spans.filter(function (span) {
|
||||
return span.endTime !== span.startTime;
|
||||
});
|
||||
|
||||
// compute duration of child spans
|
||||
new_trace.spans.forEach(function (span) {
|
||||
span.mean = Math.round(new Date(span.endTime) - new Date(span.startTime));
|
||||
delete span.endTime;
|
||||
});
|
||||
|
||||
// Update app meta (mean_latency, http_meter, db_meter, trace_count)
|
||||
new_trace.spans.forEach(function (span) {
|
||||
if (!span.name || !span.kind) return false;
|
||||
|
||||
if (span.kind === 'RPC_SERVER') {
|
||||
process.meta.histogram.update(span.mean);
|
||||
return process.meta.http_meter.update();
|
||||
}
|
||||
|
||||
// Override outbount http queries for processing
|
||||
if (span.labels && span.labels['http/method'] && span.labels['http/status_code']) {
|
||||
span.labels['service'] = span.name;
|
||||
span.name = 'outbound_http';
|
||||
}
|
||||
|
||||
for (var i = 0, len = SPANS_DB.length; i < len; i++) {
|
||||
if (span.name.indexOf(SPANS_DB[i]) > -1) {
|
||||
process.meta.db_meter.update();
|
||||
if (!process.meta.db_histograms[SPANS_DB[i]]) {
|
||||
process.meta.db_histograms[SPANS_DB[i]] = new Histogram({ measurement: 'mean' });
|
||||
}
|
||||
process.meta.db_histograms[SPANS_DB[i]].update(span.mean);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
process.meta.trace_count++;
|
||||
|
||||
/**
|
||||
* Handle traces aggregation
|
||||
*/
|
||||
if (path[0] === '/' && path !== '/') {
|
||||
path = path.substr(1, path.length - 1);
|
||||
}
|
||||
|
||||
var matched = self.matchPath(path, process.routes);
|
||||
|
||||
if (!matched) {
|
||||
process.routes[path] = [];
|
||||
self.mergeTrace(process.routes[path], new_trace, process);
|
||||
} else {
|
||||
self.mergeTrace(process.routes[matched], new_trace, process);
|
||||
}
|
||||
|
||||
return self.processes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge new trace and compute mean, min, max, count
|
||||
*
|
||||
* @param {Object} aggregated previous aggregated route
|
||||
* @param {Object} trace
|
||||
*/
|
||||
this.mergeTrace = function (aggregated, trace, process) {
|
||||
var self = this;
|
||||
|
||||
if (!aggregated || !trace) return;
|
||||
|
||||
// if the trace doesn't any spans stop aggregation here
|
||||
if (trace.spans.length === 0) return;
|
||||
|
||||
// create data structure if needed
|
||||
if (!aggregated.variances) aggregated.variances = [];
|
||||
if (!aggregated.meta) {
|
||||
aggregated.meta = {
|
||||
histogram: new Histogram({ measurement: 'median' }),
|
||||
meter: new Utility.EWMA()
|
||||
};
|
||||
}
|
||||
|
||||
aggregated.meta.histogram.update(trace.spans[0].mean);
|
||||
aggregated.meta.meter.update();
|
||||
|
||||
var merge = function (variance) {
|
||||
// no variance found so its a new one
|
||||
if (variance == null) {
|
||||
delete trace.projectId;
|
||||
delete trace.traceId;
|
||||
trace.histogram = new Histogram({ measurement: 'median' });
|
||||
trace.histogram.update(trace.spans[0].mean);
|
||||
|
||||
trace.spans.forEach(function (span) {
|
||||
span.histogram = new Histogram({ measurement: 'median' });
|
||||
span.histogram.update(span.mean);
|
||||
delete span.mean;
|
||||
});
|
||||
|
||||
// parse strackrace
|
||||
self.parseStacktrace(trace.spans);
|
||||
aggregated.variances.push(trace);
|
||||
} else {
|
||||
// check to see if request is anormally slow, if yes send it as inquisitor
|
||||
if (trace.spans[0].mean > variance.histogram.percentiles([0.95])[0.95] &&
|
||||
typeof pushInteractor !== 'undefined' && !process.initialization_timeout) {
|
||||
// serialize and add metadata
|
||||
self.parseStacktrace(trace.spans)
|
||||
var data = {
|
||||
trace: fclone(trace.spans),
|
||||
variance: fclone(variance.spans.map(function (span) {
|
||||
return {
|
||||
labels: span.labels,
|
||||
kind: span.kind,
|
||||
name: span.name,
|
||||
startTime: span.startTime,
|
||||
percentiles: {
|
||||
p5: variance.histogram.percentiles([0.5])[0.5],
|
||||
p95: variance.histogram.percentiles([0.95])[0.95]
|
||||
}
|
||||
}
|
||||
})),
|
||||
meta: {
|
||||
value: trace.spans[0].mean,
|
||||
percentiles: {
|
||||
p5: variance.histogram.percentiles([0.5])[0.5],
|
||||
p75: variance.histogram.percentiles([0.75])[0.75],
|
||||
p95: variance.histogram.percentiles([0.95])[0.95],
|
||||
p99: variance.histogram.percentiles([0.99])[0.99]
|
||||
},
|
||||
min: variance.histogram.getMin(),
|
||||
max: variance.histogram.getMax(),
|
||||
count: variance.histogram.getCount()
|
||||
},
|
||||
process: process.process
|
||||
};
|
||||
pushInteractor.bufferData('axm:transaction:outlier', data);
|
||||
}
|
||||
|
||||
// variance found, merge spans
|
||||
variance.histogram.update(trace.spans[0].mean);
|
||||
|
||||
// update duration of spans to be mean
|
||||
self.updateSpanDuration(variance.spans, trace.spans, variance.count);
|
||||
|
||||
// delete stacktrace before merging
|
||||
trace.spans.forEach(function (span) {
|
||||
delete span.labels.stacktrace;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// for every variance, check spans same variance
|
||||
for (var i = 0; i < aggregated.variances.length; i++) {
|
||||
if (self.compareList(aggregated.variances[i].spans, trace.spans)) {
|
||||
return merge(aggregated.variances[i]);
|
||||
}
|
||||
}
|
||||
// else its a new variance
|
||||
return merge(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parkour simultaneously both spans list to update value of the first one using value of the second one
|
||||
* The first should be variance already aggregated for which we want to merge the second one
|
||||
* The second one is a new trace, so we need to re-compute mean/min/max time for each spans
|
||||
*/
|
||||
this.updateSpanDuration = function (spans, newSpans) {
|
||||
for (var i = 0, len = spans.length; i < len; i++) {
|
||||
if (!newSpans[i]) continue;
|
||||
spans[i].histogram.update(newSpans[i].mean);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two spans list by going down on each span and comparing child and attribute
|
||||
*/
|
||||
this.compareList = function (one, two) {
|
||||
if (one.length !== two.length) return false;
|
||||
|
||||
for (var i = 0, len = one; i < len; i++) {
|
||||
if (one[i].name !== two[i].name) return false;
|
||||
if (one[i].kind !== two[i].kind) return false;
|
||||
if (!one[i].labels && two[i].labels) return false;
|
||||
if (one[i].labels && !two[i].labels) return false;
|
||||
if (one[i].labels.length !== two[i].labels.length) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Will return the route if we found an already matched route
|
||||
*/
|
||||
this.matchPath = function (path, routes) {
|
||||
// empty route is / without the fist slash
|
||||
if (!path || !routes) return false;
|
||||
if (path === '/') return routes[path] ? path : null;
|
||||
|
||||
// remove the last slash if exist
|
||||
if (path[path.length - 1] === '/') {
|
||||
path = path.substr(0, path.length - 1);
|
||||
}
|
||||
|
||||
// split to get array of segment
|
||||
path = path.split('/');
|
||||
|
||||
// if the path has only one segment, we just need to compare the key
|
||||
if (path.length === 1) return routes[path[0]] ? routes[path[0]] : null;
|
||||
|
||||
// check in routes already stored for match
|
||||
var keys = Object.keys(routes);
|
||||
for (var i = 0, len = keys.length; i < len; i++) {
|
||||
var route = keys[i];
|
||||
var segments = route.split('/');
|
||||
|
||||
if (segments.length !== path.length) continue;
|
||||
|
||||
for (var j = path.length - 1; j >= 0; j--) {
|
||||
// different segment, try to find if new route or not
|
||||
if (path[j] !== segments[j]) {
|
||||
// if the aggregator already have matched that segment with a wildcard and the next segment is the same
|
||||
if (self.isIdentifier(path[j]) && segments[j] === '*' && path[j - 1] === segments[j - 1]) {
|
||||
return segments.join('/');
|
||||
} // case a var in url match, so we continue because they must be other var in url
|
||||
else if (path[j - 1] !== undefined && path[j - 1] === segments[j - 1] && self.isIdentifier(path[j]) && self.isIdentifier(segments[j])) {
|
||||
segments[j] = '*';
|
||||
// update routes in cache
|
||||
routes[segments.join('/')] = routes[route];
|
||||
delete routes[keys[i]];
|
||||
return segments.join('/');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if finish to iterate over segment of path, we must be on the same route
|
||||
if (j === 0) return segments.join('/');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.launchWorker = function () {
|
||||
setInterval(function () {
|
||||
var normalized = self.prepareAggregationforShipping();
|
||||
Object.keys(normalized).forEach(function (key) {
|
||||
pushInteractor.bufferData('axm:transaction', normalized[key]);
|
||||
});
|
||||
}, cst.TRACE_FLUSH_INTERVAL);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize aggregation
|
||||
*/
|
||||
this.prepareAggregationforShipping = function () {
|
||||
var normalized = {};
|
||||
|
||||
// Iterate each applications
|
||||
Object.keys(self.processes).forEach(function (app_name) {
|
||||
var process = self.processes[app_name];
|
||||
var routes = process.routes;
|
||||
|
||||
if (process.initialization_timeout) {
|
||||
log('Waiting for app %s to be initialized', app_name);
|
||||
return null;
|
||||
}
|
||||
|
||||
normalized[app_name] = {
|
||||
data: {
|
||||
routes: [],
|
||||
meta: fclone({
|
||||
trace_count: process.meta.trace_count,
|
||||
http_meter: Math.round(process.meta.http_meter.rate(1000) * 100) / 100,
|
||||
db_meter: Math.round(process.meta.db_meter.rate(1000) * 100) / 100,
|
||||
http_percentiles: {
|
||||
median: process.meta.histogram.percentiles([0.5])[0.5],
|
||||
p95: process.meta.histogram.percentiles([0.95])[0.95],
|
||||
p99: process.meta.histogram.percentiles([0.99])[0.99]
|
||||
},
|
||||
db_percentiles: {}
|
||||
})
|
||||
},
|
||||
process: process.process
|
||||
};
|
||||
|
||||
// compute percentiles for each db spans if they exist
|
||||
SPANS_DB.forEach(function (name) {
|
||||
var histogram = process.meta.db_histograms[name];
|
||||
if (!histogram) return;
|
||||
normalized[app_name].data.meta.db_percentiles[name] = fclone(histogram.percentiles([0.5])[0.5]);
|
||||
});
|
||||
|
||||
Object.keys(routes).forEach(function (path) {
|
||||
var data = routes[path];
|
||||
|
||||
// hard check for invalid data
|
||||
if (!data.variances || data.variances.length === 0) return;
|
||||
|
||||
// get top 5 variances of the same route
|
||||
var variances = data.variances.sort(function (a, b) {
|
||||
return b.count - a.count;
|
||||
}).slice(0, 5);
|
||||
|
||||
// create a copy without reference to stored one
|
||||
var routeCopy = {
|
||||
path: path === '/' ? '/' : '/' + path,
|
||||
meta: fclone({
|
||||
min: data.meta.histogram.getMin(),
|
||||
max: data.meta.histogram.getMax(),
|
||||
count: data.meta.histogram.getCount(),
|
||||
meter: Math.round(data.meta.meter.rate(1000) * 100) / 100,
|
||||
median: data.meta.histogram.percentiles([0.5])[0.5],
|
||||
p95: data.meta.histogram.percentiles([0.95])[0.95]
|
||||
}),
|
||||
variances: []
|
||||
};
|
||||
|
||||
variances.forEach(function (variance) {
|
||||
// hard check for invalid data
|
||||
if (!variance.spans || variance.spans.length === 0) return;
|
||||
|
||||
// deep copy of variances data
|
||||
var tmp = fclone({
|
||||
spans: [],
|
||||
count: variance.histogram.getCount(),
|
||||
min: variance.histogram.getMin(),
|
||||
max: variance.histogram.getMax(),
|
||||
median: variance.histogram.percentiles([0.5])[0.5],
|
||||
p95: variance.histogram.percentiles([0.95])[0.95]
|
||||
});
|
||||
|
||||
// get data for each span
|
||||
variance.spans.forEach(function (span) {
|
||||
tmp.spans.push(fclone({
|
||||
name: span.name,
|
||||
labels: span.labels,
|
||||
kind: span.kind,
|
||||
min: span.histogram.getMin(),
|
||||
max: span.histogram.getMax(),
|
||||
median: span.histogram.percentiles([0.5])[0.5]
|
||||
}));
|
||||
});
|
||||
// push serialized into normalized data
|
||||
routeCopy.variances.push(tmp);
|
||||
});
|
||||
// push the route into normalized data
|
||||
normalized[app_name].data.routes.push(routeCopy);
|
||||
});
|
||||
});
|
||||
|
||||
return normalized;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the string can be a id of some sort
|
||||
*
|
||||
* @param {String} id
|
||||
*/
|
||||
this.isIdentifier = function (id) {
|
||||
id = typeof (id) !== 'string' ? id + '' : id;
|
||||
|
||||
// uuid v1/v4 with/without dash
|
||||
if (id.match(/[0-9a-f]{8}-[0-9a-f]{4}-[14][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{12}[14][0-9a-f]{19}/i))
|
||||
return true;
|
||||
// if number
|
||||
else if (id.match(/\d+/))
|
||||
return true;
|
||||
// if suit of nbr/letters
|
||||
else if (id.match(/[0-9]+[a-z]+|[a-z]+[0-9]+/))
|
||||
return true;
|
||||
// if match pattern with multiple char spaced by . - _ @
|
||||
else if (id.match(/((?:[0-9a-zA-Z]+[@\-_.][0-9a-zA-Z]+|[0-9a-zA-Z]+[@\-_.]|[@\-_.][0-9a-zA-Z]+)+)/))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
var REGEX_JSON_CLEANUP = /":(?!\[|{)\\"[^"]*\\"|":(["'])(?:(?=(\\?))\2.)*?\1|":(?!\[|{)[^,}\]]*|":\[[^{]*]/g
|
||||
/**
|
||||
* Cleanup trace data
|
||||
* - delete result(s)
|
||||
* - replace labels value with a question mark
|
||||
*
|
||||
* @param {Object} spans list of span for a trace
|
||||
*/
|
||||
this.censorSpans = function(spans) {
|
||||
if (!spans)
|
||||
return log('spans is null');
|
||||
if (cst.DEBUG) return;
|
||||
|
||||
spans.forEach(function(span) {
|
||||
if (!span.labels)
|
||||
return;
|
||||
|
||||
delete span.labels.results;
|
||||
delete span.labels.result;
|
||||
delete span.spanId;
|
||||
delete span.parentSpanId;
|
||||
delete span.labels.values;
|
||||
|
||||
Object.keys(span.labels).forEach(function(key) {
|
||||
if (typeof(span.labels[key]) === 'string' && key !== 'stacktrace')
|
||||
span.labels[key] = span.labels[key].replace(REGEX_JSON_CLEANUP, '\": \"?\"');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse stackrace of spans to extract and normalize data
|
||||
*
|
||||
* @param {Object} spans list of span for a trace
|
||||
*/
|
||||
this.parseStacktrace = function (spans) {
|
||||
var self = this;
|
||||
if (!spans)
|
||||
return log('spans is null');
|
||||
|
||||
spans.forEach(function (span) {
|
||||
// if empty make sure that it doesnt exist
|
||||
if (!span ||
|
||||
!span.labels ||
|
||||
!span.labels.stacktrace ||
|
||||
typeof(span.labels.stacktrace) !== 'string')
|
||||
return;
|
||||
|
||||
// you never know what come through that door
|
||||
try {
|
||||
span.labels.stacktrace = JSON.parse(span.labels.stacktrace);
|
||||
} catch (e) {
|
||||
return ;
|
||||
}
|
||||
|
||||
if (!span.labels.stacktrace || !(span.labels.stacktrace.stack_frame instanceof Array) ) return ;
|
||||
// parse the stacktrace
|
||||
var result = self.stackParser.parse(span.labels.stacktrace.stack_frame);
|
||||
if (result) {
|
||||
span.labels['source/file'] = result.callsite || undefined;
|
||||
span.labels['source/context'] = result.context || undefined;
|
||||
}
|
||||
});
|
||||
|
||||
spans.forEach(function (span) {
|
||||
if (!span || !span.labels)
|
||||
return;
|
||||
delete span.labels.stacktrace;
|
||||
})
|
||||
}
|
||||
};
|
||||
@ -1,235 +0,0 @@
|
||||
var path = require('path');
|
||||
var isAbsolute = require('../tools/IsAbsolute.js');
|
||||
|
||||
// EWMA = ExponentiallyWeightedMovingAverage from
|
||||
// https://github.com/felixge/node-measured/blob/master/lib/util/ExponentiallyMovingWeightedAverage.js
|
||||
// used to compute the nbr of time per minute that a variance is hit by a new trace
|
||||
function EWMA () {
|
||||
this._timePeriod = 60000
|
||||
this._tickInterval = 5000
|
||||
this._alpha = 1 - Math.exp(-this._tickInterval / this._timePeriod)
|
||||
this._count = 0
|
||||
this._rate = 0
|
||||
|
||||
var self = this
|
||||
this._interval = setInterval(function () {
|
||||
self.tick()
|
||||
}, this._tickInterval)
|
||||
this._interval.unref()
|
||||
}
|
||||
|
||||
EWMA.prototype.update = function (n) {
|
||||
this._count += n || 1
|
||||
}
|
||||
|
||||
EWMA.prototype.tick = function () {
|
||||
var instantRate = this._count / this._tickInterval
|
||||
this._count = 0
|
||||
|
||||
this._rate += (this._alpha * (instantRate - this._rate))
|
||||
}
|
||||
|
||||
EWMA.prototype.rate = function (timeUnit) {
|
||||
return (this._rate || 0) * timeUnit
|
||||
}
|
||||
|
||||
var moment = require('moment');
|
||||
|
||||
/**
|
||||
* Simple cache implementation
|
||||
*
|
||||
* @param {Object} opts cache options
|
||||
* @param {Integer} opts.ttl time to live of all the keys
|
||||
* @param {Function} opts.miss function called when a key isn't found in the cache
|
||||
*/
|
||||
function Cache (opts) {
|
||||
this._cache = {};
|
||||
this._miss = opts.miss;
|
||||
this._ttl_time = opts.ttl;
|
||||
this._ttl = {};
|
||||
|
||||
if (opts.ttl) {
|
||||
setInterval(this._worker.bind(this), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task running to check TTL and potentially remove older key
|
||||
*/
|
||||
Cache.prototype._worker = function () {
|
||||
var keys = Object.keys(this._ttl);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var value = this._ttl[key];
|
||||
if (moment().isAfter(value)) {
|
||||
delete this._cache[key];
|
||||
delete this._ttl[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Empty the cache
|
||||
*/
|
||||
Cache.prototype.reset = function () {
|
||||
this._cache = null;
|
||||
this._cache = {};
|
||||
this._ttl = null;
|
||||
this._ttl = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a value from the cache
|
||||
*
|
||||
* @param {String} key
|
||||
*/
|
||||
Cache.prototype.get = function (key) {
|
||||
if (!key) return null;
|
||||
var value = this._cache[key];
|
||||
if (value) return value;
|
||||
|
||||
value = this._miss(key);
|
||||
|
||||
if (value) {
|
||||
this.set(key, value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a value in the cache
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Mixed} value
|
||||
*/
|
||||
Cache.prototype.set = function (key, value) {
|
||||
if (!key || !value) return false;
|
||||
this._cache[key] = value;
|
||||
if (this._ttl_time) {
|
||||
this._ttl[key] = moment().add(this._ttl_time, 'seconds');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* StackTraceParser is used to parse callsite from stacktrace
|
||||
* and get from FS the context of the error (if available)
|
||||
*
|
||||
* @param {Cache} cache cache implementation used to query file from FS and get context
|
||||
*/
|
||||
function StackTraceParser (opts) {
|
||||
this._cache = opts.cache;
|
||||
this._context_size = opts.context;
|
||||
}
|
||||
|
||||
StackTraceParser.prototype.attachContext = function (error) {
|
||||
var self = this;
|
||||
if (!error) return error;
|
||||
|
||||
// if pmx attached callsites we can parse them to retrieve the context
|
||||
if (typeof (error.stackframes) === 'object') {
|
||||
var result = self.parse(error.stackframes);
|
||||
// no need to send it since there is already the stacktrace
|
||||
delete error.stackframes;
|
||||
delete error.__error_callsites;
|
||||
|
||||
if (result) {
|
||||
error.callsite = result.callsite;
|
||||
error.context = result.context;
|
||||
}
|
||||
}
|
||||
// if the stack is here we can parse it directly from the stack string
|
||||
// only if the context has been retrieved from elsewhere
|
||||
if (typeof error.stack === 'string' && !error.callsite) {
|
||||
var siteRegex = /(\/[^\\\n]*)/g;
|
||||
var tmp;
|
||||
var stack = [];
|
||||
|
||||
// find matching groups
|
||||
while ((tmp = siteRegex.exec(error.stack))) {
|
||||
stack.push(tmp[1]);
|
||||
}
|
||||
|
||||
// parse each callsite to match the format used by the stackParser
|
||||
stack = stack.map(function (callsite) {
|
||||
// remove the trailing ) if present
|
||||
if (callsite[callsite.length - 1] === ')') {
|
||||
callsite = callsite.substr(0, callsite.length - 1);
|
||||
}
|
||||
var location = callsite.split(':');
|
||||
|
||||
return location.length < 3 ? callsite : {
|
||||
file_name: location[0],
|
||||
line_number: parseInt(location[1])
|
||||
};
|
||||
});
|
||||
|
||||
var finalCallsite = self.parse(stack);
|
||||
if (finalCallsite) {
|
||||
error.callsite = finalCallsite.callsite;
|
||||
error.context = finalCallsite.context;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the stacktrace and return callsite + context if available
|
||||
*/
|
||||
StackTraceParser.prototype.parse = function (stack) {
|
||||
var self = this;
|
||||
if (!stack || stack.length === 0) return false;
|
||||
|
||||
for (var i = 0, len = stack.length; i < len; i++) {
|
||||
var callsite = stack[i];
|
||||
|
||||
// avoid null values
|
||||
if (typeof callsite !== 'object') continue;
|
||||
if (!callsite.file_name || !callsite.line_number) continue;
|
||||
|
||||
var type = isAbsolute(callsite.file_name) || callsite.file_name[0] === '.' ? 'user' : 'core';
|
||||
|
||||
// only use the callsite if its inside user space
|
||||
if (!callsite || type === 'core' || callsite.file_name.indexOf('node_modules') > -1 ||
|
||||
callsite.file_name.indexOf('vxx') > -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the whole context (all lines) and cache them if necessary
|
||||
var context = self._cache.get(callsite.file_name);
|
||||
var source = [];
|
||||
if (context && context.length > 0) {
|
||||
// get line before the call
|
||||
var preLine = callsite.line_number - self._context_size - 1;
|
||||
var pre = context.slice(preLine > 0 ? preLine : 0, callsite.line_number - 1);
|
||||
if (pre && pre.length > 0) {
|
||||
pre.forEach(function (line) {
|
||||
source.push(line.replace(/\t/g, ' '));
|
||||
});
|
||||
}
|
||||
// get the line where the call has been made
|
||||
if (context[callsite.line_number - 1]) {
|
||||
source.push(context[callsite.line_number - 1].replace(/\t/g, ' ').replace(' ', '>>'));
|
||||
}
|
||||
// and get the line after the call
|
||||
var postLine = callsite.line_number + self._context_size;
|
||||
var post = context.slice(callsite.line_number, postLine);
|
||||
if (post && post.length > 0) {
|
||||
post.forEach(function (line) {
|
||||
source.push(line.replace(/\t/g, ' '));
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
context: source.length > 0 ? source.join('\n') : 'cannot retrieve source context',
|
||||
callsite: [ callsite.file_name, callsite.line_number ].join(':')
|
||||
};
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
EWMA: EWMA,
|
||||
Cache: Cache,
|
||||
StackTraceParser: StackTraceParser
|
||||
};
|
||||
@ -1,69 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
var PM2 = require('../..');
|
||||
var debug = require('debug')('interface:watchdog');
|
||||
var shelljs = require('shelljs');
|
||||
var csts = require('../../constants');
|
||||
var path = require('path');
|
||||
|
||||
process.env.PM2_AGENT_ONLINE = true;
|
||||
|
||||
var WatchDog = module.exports = {
|
||||
start : function(p) {
|
||||
var self = this;
|
||||
this.ipm2 = p.conf.ipm2;
|
||||
this.relaunching = false;
|
||||
this.pm2_instance = p.conf.pm2_instance;
|
||||
|
||||
/**
|
||||
* Handle PM2 connection state changes
|
||||
*/
|
||||
this.ipm2.on('ready', function() {
|
||||
console.log('[WATCHDOG] Connected to PM2');
|
||||
self.relaunching = false;
|
||||
self.autoDump();
|
||||
});
|
||||
|
||||
console.log('[WATCHDOG] Launching');
|
||||
|
||||
this.ipm2.on('reconnecting', function() {
|
||||
console.log('[WATCHDOG] PM2 is disconnected - Relaunching PM2');
|
||||
|
||||
if (self.relaunching === true) return console.log('[WATCHDOG] Already relaunching PM2');
|
||||
self.relaunching = true;
|
||||
|
||||
if (self.dump_interval)
|
||||
clearInterval(self.dump_interval);
|
||||
|
||||
return WatchDog.resurrect();
|
||||
});
|
||||
},
|
||||
resurrect : function() {
|
||||
var self = this;
|
||||
|
||||
console.log('[WATCHDOG] Trying to launch PM2 #1');
|
||||
|
||||
shelljs.exec('node ' + path.resolve(__dirname, '../../bin/pm2') + ' resurrect', function() {
|
||||
setTimeout(function() {
|
||||
self.relaunching = false;
|
||||
}, 2500);
|
||||
});
|
||||
},
|
||||
autoDump : function() {
|
||||
var self = this;
|
||||
|
||||
this.dump_interval = setInterval(function() {
|
||||
if (self.relaunching == true) return false;
|
||||
|
||||
self.pm2_instance.dump(function(err) {
|
||||
if (err) return console.error('[WATCHDOG] Error when dumping');
|
||||
debug('PM2 process list dumped');
|
||||
return false;
|
||||
});
|
||||
}, 5 * 60 * 1000);
|
||||
}
|
||||
};
|
||||
@ -1,40 +0,0 @@
|
||||
var os = require('os');
|
||||
|
||||
var type = {
|
||||
v4: {
|
||||
def: '127.0.0.1',
|
||||
family: 'IPv4'
|
||||
},
|
||||
v6: {
|
||||
def: '::1',
|
||||
family: 'IPv6'
|
||||
}
|
||||
};
|
||||
|
||||
function internalIp(version) {
|
||||
var options = type[version];
|
||||
var ret = options.def;
|
||||
var interfaces = os.networkInterfaces();
|
||||
|
||||
Object.keys(interfaces).forEach(function (el) {
|
||||
interfaces[el].forEach(function (el2) {
|
||||
if (!el2.internal && el2.family === options.family) {
|
||||
ret = el2.address;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function v4() {
|
||||
return internalIp('v4');
|
||||
}
|
||||
|
||||
function v6() {
|
||||
return internalIp('v6');
|
||||
}
|
||||
|
||||
module.exports = v4;
|
||||
module.exports.v4 = v4;
|
||||
module.exports.v6 = v6;
|
||||
@ -1,122 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var axon = require('pm2-axon');
|
||||
var cst = require('../../constants.js');
|
||||
var util = require('util');
|
||||
var rpc = require('pm2-axon-rpc');
|
||||
var log = require('debug')('pm2:interface');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
/**
|
||||
* Export with conf
|
||||
*/
|
||||
module.exports = function(opts){
|
||||
var sub_port = opts && opts.sub_port || cst.DAEMON_PUB_PORT;
|
||||
var rpc_port = opts && opts.rpc_port || cst.DAEMON_RPC_PORT;
|
||||
|
||||
return new IPM2(sub_port, rpc_port);
|
||||
};
|
||||
|
||||
/**
|
||||
* IPM2, Pm2 Interface
|
||||
*/
|
||||
|
||||
var IPM2 = function(sub_port, rpc_port) {
|
||||
if (!(this instanceof IPM2)) return new IPM2(sub_port, rpc_port);
|
||||
var self = this;
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.sub_port = sub_port;
|
||||
this.rpc_port = rpc_port;
|
||||
|
||||
|
||||
var sub = axon.socket('sub-emitter');
|
||||
var sub_sock = this.sub_sock = sub.connect(sub_port);
|
||||
this.bus = sub;
|
||||
|
||||
var req = axon.socket('req');
|
||||
var rpc_sock = this.rpc_sock = req.connect(rpc_port);
|
||||
this.rpc_client = new rpc.Client(req);
|
||||
|
||||
this.rpc = {};
|
||||
|
||||
rpc_sock.on('connect', function() {
|
||||
log('rpc_sock:ready');
|
||||
self.emit('rpc_sock:ready');
|
||||
generateMethods(function() {
|
||||
self.emit('ready');
|
||||
});
|
||||
});
|
||||
|
||||
rpc_sock.on('close', function() {
|
||||
log('rpc_sock:closed');
|
||||
self.emit('close');
|
||||
});
|
||||
|
||||
rpc_sock.on('reconnect attempt', function() {
|
||||
log('rpc_sock:reconnecting');
|
||||
self.emit('reconnecting');
|
||||
});
|
||||
|
||||
sub_sock.on('connect', function() {
|
||||
log('sub_sock ready');
|
||||
self.emit('sub_sock:ready');
|
||||
});
|
||||
|
||||
sub_sock.on('close', function() {
|
||||
log('sub_sock:closed');
|
||||
self.emit('closed');
|
||||
});
|
||||
|
||||
sub_sock.on('reconnect attempt', function() {
|
||||
log('sub_sock:reconnecting');
|
||||
self.emit('reconnecting');
|
||||
});
|
||||
|
||||
/**
|
||||
* Disconnect socket connections. This will allow Node to exit automatically.
|
||||
* Further calls to PM2 from this object will throw an error.
|
||||
*/
|
||||
this.disconnect = function () {
|
||||
self.sub_sock.close();
|
||||
self.rpc_sock.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate method by requesting exposed methods by PM2
|
||||
* You can now control/interact with PM2
|
||||
*/
|
||||
var generateMethods = function(cb) {
|
||||
log('Requesting and generating RPC methods');
|
||||
self.rpc_client.methods(function(err, methods) {
|
||||
Object.keys(methods).forEach(function(key) {
|
||||
var method_signature, md;
|
||||
method_signature = md = methods[key];
|
||||
|
||||
log('+-- Creating %s method', md.name);
|
||||
|
||||
(function(name) {
|
||||
self.rpc[name] = function() {
|
||||
log(name);
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift(name);
|
||||
self.rpc_client.call.apply(self.rpc_client, args);
|
||||
};
|
||||
})(md.name);
|
||||
|
||||
});
|
||||
return cb();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
util.inherits(IPM2, EventEmitter);
|
||||
@ -30,15 +30,7 @@ delete process.env.pm2_env;
|
||||
(function ProcessContainer() {
|
||||
var fs = require('fs');
|
||||
|
||||
if (process.env.pmx !== 'false') {
|
||||
require('pmx').init({
|
||||
transactions: (process.env.km_link === 'true' && (process.env.trace === 'true' || process.env.deep_monitoring === 'true')) || false,
|
||||
http: process.env.km_link === 'true' || false,
|
||||
v8: process.env.v8 === 'true' || process.env.deep_monitoring === 'true' || false,
|
||||
event_loop_dump: process.env.event_loop_inspector === 'true' || process.env.deep_monitoring === 'true' || false,
|
||||
deep_metrics: process.env.deep_monitoring === 'true' || false
|
||||
});
|
||||
}
|
||||
require('./ProcessUtils').injectModules();
|
||||
|
||||
var stdFile = pm2_env.pm_log_path;
|
||||
var outFile = pm2_env.pm_out_log_path;
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
/**
|
||||
/**
|
||||
* Copyright 2013 the PM2 project authors. All rights reserved.
|
||||
* Use of this source code is governed by a license that
|
||||
* can be found in the LICENSE file.
|
||||
*/
|
||||
// Inject custom modules
|
||||
if (process.env.pmx !== 'false') {
|
||||
require('pmx').init({
|
||||
transactions: (process.env.km_link === 'true' && (process.env.trace === 'true' || process.env.deep_monitoring === 'true')) || false,
|
||||
http: process.env.km_link === 'true' || false,
|
||||
v8: process.env.v8 === 'true' || process.env.deep_monitoring === 'true' || false,
|
||||
event_loop_dump: process.env.event_loop_inspector === 'true' || process.env.deep_monitoring === 'true' || false,
|
||||
deep_metrics: process.env.deep_monitoring === 'true' || false
|
||||
});
|
||||
}
|
||||
require('./ProcessUtils').injectModules();
|
||||
|
||||
if (typeof(process.env.source_map_support) != "undefined" &&
|
||||
process.env.source_map_support !== "false") {
|
||||
|
||||
43
lib/ProcessUtils.js
Normal file
43
lib/ProcessUtils.js
Normal file
@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
injectModules: function() {
|
||||
if (process.env.pmx !== 'false') {
|
||||
const pmx = require('@pm2/io');
|
||||
|
||||
let conf = {};
|
||||
|
||||
if (process.env.io) {
|
||||
const io = JSON.parse(process.env.io);
|
||||
conf = io.conf ? io.conf : conf;
|
||||
}
|
||||
|
||||
let defaultConf = {
|
||||
transactions: (process.env.trace === 'true' || process.env.deep_monitoring === 'true') || false,
|
||||
http: process.env.km_link === 'true' || false,
|
||||
v8: process.env.v8 === 'true' || process.env.deep_monitoring === 'true' || false,
|
||||
event_loop_dump: process.env.event_loop_inspector === 'true' || process.env.deep_monitoring === 'true' || false,
|
||||
deep_metrics: process.env.deep_monitoring === 'true' || false
|
||||
};
|
||||
|
||||
const mergedConf = Object.assign(defaultConf, conf);
|
||||
|
||||
pmx.init(mergedConf);
|
||||
|
||||
if(require('semver').satisfies(process.versions.node, '>= 8.0.0')) {
|
||||
var url = '';
|
||||
pmx.action('internal:inspect', function(reply) {
|
||||
const inspector = require('inspector');
|
||||
if(url === '') {
|
||||
inspector.open();
|
||||
url = inspector.url();
|
||||
} else {
|
||||
inspector.close();
|
||||
url = '';
|
||||
}
|
||||
reply(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -215,7 +215,6 @@ Satan.remoteWrapper = function() {
|
||||
|
||||
killMe : God.killMe,
|
||||
notifyKillPM2 : God.notifyKillPM2,
|
||||
forceGc : God.forceGc,
|
||||
|
||||
findByFullPath : God.findByFullPath,
|
||||
|
||||
@ -329,7 +328,7 @@ Satan.launchDaemon = function launchDaemon(cb) {
|
||||
debug('Launching daemon');
|
||||
|
||||
var SatanJS = p.resolve(p.dirname(module.filename), 'Satan.js');
|
||||
var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js');
|
||||
var InteractorDaemonizer = require('@pm2/agent/src/InteractorClient');
|
||||
|
||||
var node_args = [];
|
||||
|
||||
@ -385,7 +384,7 @@ Satan.launchDaemon = function launchDaemon(cb) {
|
||||
debug('PM2 daemon launched with return message: ', msg);
|
||||
child.removeListener('error', onError);
|
||||
child.disconnect();
|
||||
InteractorDaemonizer.launchAndInteract({}, function(err, data) {
|
||||
InteractorDaemonizer.launchAndInteract({}, {}, function(err, data) {
|
||||
if (data)
|
||||
debug('Interactor launched');
|
||||
return cb ? cb(null, child) : false;
|
||||
|
||||
@ -247,6 +247,18 @@ var Utility = module.exports = {
|
||||
|
||||
checkPathIsNull: function(path) {
|
||||
return path === 'NULL' || path === '/dev/null';
|
||||
},
|
||||
|
||||
generateUUID: function () {
|
||||
var s = [];
|
||||
var hexDigits = "0123456789abcdef";
|
||||
for (var i = 0; i < 36; i++) {
|
||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
||||
}
|
||||
s[14] = "4";
|
||||
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
|
||||
s[8] = s[13] = s[18] = s[23] = "-";
|
||||
return s.join("");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -20,9 +20,9 @@ commander.version(pkg.version)
|
||||
.option('--auto-manage', 'keep application online after command exit')
|
||||
.option('--fast-boot', 'boot app faster by keeping pm2 runtime online in background (effective at second exit/start)')
|
||||
.option('--web [port]', 'launch process web api on [port] default to 9615')
|
||||
.option('--secret [key]', 'keymetrics secret key')
|
||||
.option('--public [key]', 'keymetrics public key')
|
||||
.option('--machine-name [name]', 'keymetrics machine name')
|
||||
.option('--secret [key]', 'PM2 plus secret key')
|
||||
.option('--public [key]', 'PM2 plus public key')
|
||||
.option('--machine-name [name]', 'PM2 plus machine name')
|
||||
.option('--env [name]', 'select env_[name] env variables in process config file')
|
||||
.option('--watch', 'Watch and Restart')
|
||||
.option('-i --instances <number>', 'launch [number] instances with load-balancer')
|
||||
@ -32,9 +32,9 @@ commander.command('*')
|
||||
.action(function(cmd){
|
||||
pm2 = new PM2.custom({
|
||||
pm2_home : path.join(process.env.HOME, '.pm3'),
|
||||
secret_key : process.env.KEYMETRICS_SECRET || commander.secret,
|
||||
public_key : process.env.KEYMETRICS_PUBLIC || commander.public,
|
||||
machine_name : process.env.INSTANCE_NAME || commander.machineName
|
||||
secret_key : cst.SECRET_KEY || commander.secret,
|
||||
public_key : cst.PUBLIC_KEY || commander.public,
|
||||
machine_name : cst.MACHINE_NAME || commander.machineName
|
||||
});
|
||||
|
||||
pm2.connect(function() {
|
||||
|
||||
@ -17,15 +17,15 @@ process.env.PM2_DISCRETE_MODE = true;
|
||||
commander.version(pkg.version)
|
||||
.description('pm2-runtime is a drop-in replacement Node.js binary for containers')
|
||||
.option('-i --instances <number>', 'launch [number] of processes automatically load-balanced. Increase overall performances and performance stability.')
|
||||
.option('--secret [key]', '[MONITORING] keymetrics secret key')
|
||||
.option('--secret [key]', '[MONITORING] PM2 plus secret key')
|
||||
.option('--no-autorestart', 'start an app without automatic restart')
|
||||
.option('--node-args <node_args>', 'space delimited arguments to pass to node in cluster mode - e.g. --node-args="--debug=7001 --trace-deprecation"')
|
||||
.option('-n --name <name>', 'set a <name> for script')
|
||||
.option('--max-memory-restart <memory>', 'specify max memory amount used to autorestart (in octet or use syntax like 100M)')
|
||||
.option('-c --cron <cron_pattern>', 'restart a running process based on a cron pattern')
|
||||
.option('--interpreter <interpreter>', 'the interpreter pm2 should use for executing app (bash, python...)')
|
||||
.option('--public [key]', '[MONITORING] keymetrics public key')
|
||||
.option('--machine-name [name]', '[MONITORING] keymetrics machine name')
|
||||
.option('--public [key]', '[MONITORING] PM2 plus public key')
|
||||
.option('--machine-name [name]', '[MONITORING] PM2 plus machine name')
|
||||
.option('--trace', 'enable transaction tracing with km')
|
||||
.option('--v8', 'enable v8 data collecting')
|
||||
.option('--format', 'output logs formated like key=val')
|
||||
@ -65,9 +65,9 @@ var Runtime = {
|
||||
instanciate : function(cmd) {
|
||||
this.pm2 = new PM2.custom({
|
||||
pm2_home : process.env.PM2_HOME || path.join(process.env.HOME, '.pm2'),
|
||||
secret_key : process.env.KEYMETRICS_SECRET || commander.secret,
|
||||
public_key : process.env.KEYMETRICS_PUBLIC || commander.public,
|
||||
machine_name : process.env.INSTANCE_NAME || commander.machineName,
|
||||
secret_key : cst.SECRET_KEY || commander.secret,
|
||||
public_key : cst.PUBLIC_KEY || commander.public,
|
||||
machine_name : cst.MACHINE_NAME || commander.machineName,
|
||||
daemon_mode : process.env.PM2_RUNTIME_DEBUG || false
|
||||
});
|
||||
|
||||
|
||||
5
lib/motd
5
lib/motd
@ -12,12 +12,11 @@ __/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____
|
||||
_\///______________\///______________\///__\///////////////__
|
||||
|
||||
|
||||
Community Edition
|
||||
Runtime Edition
|
||||
|
||||
Production Process Manager for Node.js applications
|
||||
PM2 is a Production Process Manager for Node.js applications
|
||||
with a built-in Load Balancer.
|
||||
|
||||
|
||||
Start and Daemonize any application:
|
||||
$ pm2 start app.js
|
||||
|
||||
|
||||
@ -1,33 +1,15 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* Application configuration section
|
||||
* http://pm2.keymetrics.io/docs/usage/application-declaration/
|
||||
*/
|
||||
apps : [
|
||||
|
||||
// First application
|
||||
{
|
||||
name : 'API',
|
||||
script : 'app.js',
|
||||
env: {
|
||||
COMMON_VARIABLE: 'true'
|
||||
},
|
||||
env_production : {
|
||||
NODE_ENV: 'production'
|
||||
}
|
||||
apps : [{
|
||||
name : 'API',
|
||||
script : 'app.js',
|
||||
env: {
|
||||
NODE_ENV: 'development'
|
||||
},
|
||||
|
||||
// Second application
|
||||
{
|
||||
name : 'WEB',
|
||||
script : 'web.js'
|
||||
env_production : {
|
||||
NODE_ENV: 'production'
|
||||
}
|
||||
],
|
||||
}],
|
||||
|
||||
/**
|
||||
* Deployment section
|
||||
* http://pm2.keymetrics.io/docs/usage/deployment/
|
||||
*/
|
||||
deploy : {
|
||||
production : {
|
||||
user : 'node',
|
||||
@ -36,17 +18,6 @@ module.exports = {
|
||||
repo : 'git@github.com:repo.git',
|
||||
path : '/var/www/production',
|
||||
'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production'
|
||||
},
|
||||
dev : {
|
||||
user : 'node',
|
||||
host : '212.83.163.1',
|
||||
ref : 'origin/master',
|
||||
repo : 'git@github.com:repo.git',
|
||||
path : '/var/www/development',
|
||||
'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env dev',
|
||||
env : {
|
||||
NODE_ENV: 'dev'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
41
lib/templates/init-scripts/rcd-openbsd.tpl
Normal file
41
lib/templates/init-scripts/rcd-openbsd.tpl
Normal file
@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# from /usr/ports/infrastructure/templates/rc.template
|
||||
|
||||
daemon="/usr/local/bin/pm2"
|
||||
#daemon_flags=
|
||||
#daemon_rtable=0
|
||||
#daemon_timeout="30"
|
||||
daemon_user="%USER%"
|
||||
|
||||
. /etc/rc.d/rc.subr
|
||||
|
||||
pexp="node: PM2.*God Daemon.*"
|
||||
#rc_bg= # (undefined)
|
||||
#rc_reload= # (undefined)
|
||||
#rc_usercheck=YES
|
||||
|
||||
#rc_pre() {
|
||||
#}
|
||||
|
||||
rc_start() {
|
||||
${rcexec} "${daemon} ${daemon_flags} resurrect"
|
||||
}
|
||||
|
||||
#rc_check() {
|
||||
# pgrep -T "${daemon_rtable}" -q -xf "${pexp}"
|
||||
#}
|
||||
|
||||
rc_reload() {
|
||||
${rcexec} "${daemon} reload all"
|
||||
#pkill -HUP -T "${daemon_rtable}" -xf "${pexp}"
|
||||
}
|
||||
|
||||
#rc_stop() {
|
||||
# pkill -T "${daemon_rtable}" -xf "${pexp}"
|
||||
#}
|
||||
|
||||
#rc_post() {
|
||||
#}
|
||||
|
||||
rc_cmd $1
|
||||
@ -18,7 +18,7 @@ module.exports = function(bytes, size) {
|
||||
}
|
||||
var descriptor = fs.openSync(file, 'r');
|
||||
try {
|
||||
bytes = new Buffer(max_bytes);
|
||||
bytes = Buffer.alloc(max_bytes);
|
||||
size = fs.readSync(descriptor, bytes, 0, bytes.length, 0);
|
||||
} finally {
|
||||
fs.closeSync(descriptor);
|
||||
@ -32,7 +32,7 @@ module.exports = function(bytes, size) {
|
||||
|
||||
fs.open(file, 'r', function(err, descriptor){
|
||||
if (err) return callback(err);
|
||||
var bytes = new Buffer(max_bytes);
|
||||
var bytes = Buffer.alloc(max_bytes);
|
||||
// Read the file with no encoding for raw buffer access.
|
||||
fs.read(descriptor, bytes, 0, bytes.length, 0, function(err, size, bytes){
|
||||
fs.close(descriptor, function(err2){
|
||||
|
||||
68
package.json
68
package.json
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "pm2",
|
||||
"preferGlobal": true,
|
||||
"version": "2.10.4",
|
||||
"version": "3.0.0",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
"directories": {
|
||||
"bin": "./bin",
|
||||
@ -12,25 +12,25 @@
|
||||
},
|
||||
"author": {
|
||||
"name": "Strzelewicz Alexandre",
|
||||
"email": "alexandre@keymetrics.io",
|
||||
"url": "https://keymetrics.io"
|
||||
"email": "alexandre@pm2.io",
|
||||
"url": "https://pm2.io"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "tknew",
|
||||
"email": "strzelewicz.alexandre@gmail.com"
|
||||
"name": "Alexandre Strzelewicz",
|
||||
"email": "alexandre@pm2.io"
|
||||
},
|
||||
{
|
||||
"name": "soyuka",
|
||||
"email": "soyuka@gmail.com"
|
||||
"name": "Antoine Bluchet",
|
||||
"email": "antoine@pm2.io"
|
||||
},
|
||||
{
|
||||
"name": "wallet77",
|
||||
"email": "wallet77@gmail.com"
|
||||
"name": "Vincent Vallet",
|
||||
"email": "vincent@pm2.io"
|
||||
},
|
||||
{
|
||||
"name": "vmarchaud",
|
||||
"email": "contact@vmarchaud.fr"
|
||||
"name": "Valentin Marchaud",
|
||||
"email": "valentin@pm2.io"
|
||||
}
|
||||
],
|
||||
"contributors": [
|
||||
@ -91,8 +91,7 @@
|
||||
"main": "index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "NODE_ENV=test bash test/pm2_check_dependencies.sh && NODE_ENV=test bash test/pm2_programmatic_tests.sh && NODE_ENV=test bash test/pm2_behavior_tests.sh",
|
||||
"bench-pmx": "pm2 delete all; pm2 install pm2-probe; node examples/pmx/app.js; pm2 ls"
|
||||
"test": "bash test/e2e.sh && bash test/unit.sh"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
@ -159,38 +158,41 @@
|
||||
"pm2-runtime": "./bin/pm2-runtime"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^2.5",
|
||||
"@pm2/agent": "^0.5.4",
|
||||
"@pm2/io": "^2.0.2",
|
||||
"@pm2/js-api": "^0.5.15",
|
||||
"async": "^2.6.1",
|
||||
"blessed": "^0.1.81",
|
||||
"chalk": "^1.1",
|
||||
"chokidar": "^2",
|
||||
"chalk": "^2.4.1",
|
||||
"chokidar": "^2.0.4",
|
||||
"cli-table-redemption": "^1.0.0",
|
||||
"commander": "2.13.0",
|
||||
"coffee-script": "^1.12.7",
|
||||
"commander": "2.15.1",
|
||||
"cron": "^1.3",
|
||||
"debug": "^3.0",
|
||||
"eventemitter2": "1.0.5",
|
||||
"debug": "^3.1",
|
||||
"eventemitter2": "5.0.1",
|
||||
"fclone": "1.0.11",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "^2.19",
|
||||
"needle": "^2.1.0",
|
||||
"moment": "^2.22.2",
|
||||
"needle": "^2.2.1",
|
||||
"nssocket": "0.6.0",
|
||||
"pidusage": "^1.2.0",
|
||||
"pm2-axon": "3.1.0",
|
||||
"pidusage": "^2.0.6",
|
||||
"pm2-axon": "3.3.0",
|
||||
"pm2-axon-rpc": "^0.5.1",
|
||||
"pm2-deploy": "^0.3.9",
|
||||
"pm2-multimeter": "^0.1.2",
|
||||
"pmx": "^1.6",
|
||||
"promptly": "2.2.0",
|
||||
"semver": "^5.3",
|
||||
"shelljs": "0.7.8",
|
||||
"source-map-support": "^0.5",
|
||||
"promptly": "3.0.3",
|
||||
"semver": "^5.5",
|
||||
"shelljs": "~0.8.2",
|
||||
"source-map-support": "^0.5.6",
|
||||
"sprintf-js": "1.1.1",
|
||||
"v8-compile-cache": "^1.1.0",
|
||||
"vizion": "^0.2",
|
||||
"v8-compile-cache": "^2.0.0",
|
||||
"vizion": "~0.2.0",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^3.5",
|
||||
"should": "^11"
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"gkt": "https://tgz.pm2.io/gkt-1.0.0.tgz"
|
||||
|
||||
2
paths.js
2
paths.js
@ -45,7 +45,7 @@ module.exports = function(PM2_HOME) {
|
||||
DEFAULT_PID_PATH : p.resolve(PM2_HOME, 'pids'),
|
||||
DEFAULT_LOG_PATH : p.resolve(PM2_HOME, 'logs'),
|
||||
DEFAULT_MODULE_PATH : p.resolve(PM2_HOME, 'modules'),
|
||||
KM_ACCESS_TOKEN : p.resolve(PM2_HOME, 'km-access-token'),
|
||||
PM2_IO_ACCESS_TOKEN : p.resolve(PM2_HOME, 'pm2-io-token'),
|
||||
DUMP_FILE_PATH : p.resolve(PM2_HOME, 'dump.pm2'),
|
||||
DUMP_BACKUP_FILE_PATH : p.resolve(PM2_HOME, 'dump.pm2.bak'),
|
||||
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
FROM node:9
|
||||
FROM node:alpine
|
||||
|
||||
RUN mkdir -p /var/pm2
|
||||
|
||||
WORKDIR /var/pm2
|
||||
|
||||
ENV NODE_ENV test
|
||||
ENV PM2_DISCRETE_MODE true
|
||||
|
||||
RUN apk update && apk add bash git curl python python3 php5 && rm -rf /var/cache/apk/*
|
||||
RUN ln -s /usr/bin/php5 /usr/bin/php
|
||||
RUN npm install -g mocha@3.5
|
||||
|
||||
CMD ["mocha", "./test/programmatic/api.mocha.js"]
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
|
||||
|
||||
echo -e "\033[1mRunning tests:\033[0m"
|
||||
|
||||
which wrk
|
||||
spec "You should have wrk benchmark in your /usr/bin"
|
||||
|
||||
killall node
|
||||
|
||||
cd $file_path
|
||||
$pm2 start cluster-pm2.json
|
||||
$pm2 start cluster-pm2.json -f
|
||||
$pm2 start cluster-pm2.json -f
|
||||
$pm2 start cluster-pm2.json -f
|
||||
spec "start cluster"
|
||||
|
||||
wrk -c 500 -t 500 -d 8 http://localhost:8020 &> /dev/null &
|
||||
$pm2 monit
|
||||
$pm2 list
|
||||
$pm2 stop
|
||||
@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# LSOF check
|
||||
#
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
|
||||
cd $file_path
|
||||
|
||||
echo "################## RELOAD ###################"
|
||||
|
||||
lsof -c PM2 > /tmp/no_pm2_out.dat
|
||||
|
||||
$pm2 list
|
||||
|
||||
sleep 3
|
||||
lsof -c PM2 > /tmp/empty_pm2_out.dat
|
||||
|
||||
$pm2 start echo.js -i 3
|
||||
$pm2 start killtoofast.js -i 3
|
||||
$pm2 delete all
|
||||
|
||||
sleep 3
|
||||
lsof -c PM2 > /tmp/empty_pm2_out2.dat
|
||||
|
||||
OUT1=`cat /tmp/empty_pm2_out.dat | wc -l`
|
||||
OUT2=`cat /tmp/empty_pm2_out2.dat | wc -l`
|
||||
|
||||
if [ $OUT1 -eq $OUT2 ]; then
|
||||
success "All file descriptors have been closed"
|
||||
else
|
||||
fail "Some file descriptors are still open"
|
||||
fi
|
||||
|
||||
$pm2 start killtoofast.js -i 6
|
||||
$pm2 kill
|
||||
|
||||
sleep 3
|
||||
lsof -c PM2 > /tmp/no_pm2_out2.dat
|
||||
diff /tmp/no_pm2_out.dat /tmp/no_pm2_out2.dat
|
||||
|
||||
if [ $? == "0" ]; then
|
||||
success "All file descriptors have been closed"
|
||||
else
|
||||
fail "Some file descriptors are still open"
|
||||
fi
|
||||
|
||||
rm /tmp/no_pm2_out.dat
|
||||
rm /tmp/no_pm2_out2.dat
|
||||
rm /tmp/empty_pm2_out.dat
|
||||
rm /tmp/empty_pm2_out2.dat
|
||||
@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
|
||||
cd $file_path
|
||||
|
||||
echo "################## GRACEFUL RELOAD ###################"
|
||||
|
||||
###############
|
||||
|
||||
echo "Launching"
|
||||
$pm2 start graceful-exit.js -i 4 --name="graceful" -o "grace.log" -e "grace-err.log"
|
||||
should 'should start processes' 'online' 4
|
||||
|
||||
OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"`
|
||||
cat /dev/null > $OUT_LOG
|
||||
|
||||
#### Graceful reload all
|
||||
|
||||
$pm2 gracefulReload all
|
||||
|
||||
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
|
||||
[ $OUT -eq 1 ] || fail "Process not restarted gracefuly"
|
||||
success "Process restarted gracefuly"
|
||||
|
||||
|
||||
cat /dev/null > $OUT_LOG
|
||||
|
||||
#### Graceful reload name
|
||||
$pm2 gracefulReload graceful
|
||||
|
||||
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
|
||||
[ $OUT -eq 1 ] || fail "Process not restarted gracefuly"
|
||||
success "Process restarted gracefuly"
|
||||
@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
|
||||
cd $file_path
|
||||
|
||||
echo "################## GRACEFUL RELOAD 2 ###################"
|
||||
|
||||
echo "Launching"
|
||||
$pm2 start graceful-exit-no-listen.js -i 2 --name="graceful2" -o "grace2.log" -e "grace-err2.log"
|
||||
should 'should start processes' 'online' 2
|
||||
|
||||
OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"`
|
||||
cat /dev/null > $OUT_LOG
|
||||
|
||||
#### Graceful reload name
|
||||
$pm2 gracefulReload graceful2
|
||||
|
||||
echo "PATH: $OUT_LOG"
|
||||
|
||||
TEXT=$(cat $OUT_LOG)
|
||||
|
||||
echo "TEXT: $TEXT"
|
||||
|
||||
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
|
||||
[ $OUT -eq 1 ] || fail "Non-listening process not restarted gracefuly"
|
||||
success "Non-listening process restarted gracefuly"
|
||||
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
|
||||
cd $file_path
|
||||
|
||||
echo "################## GRACEFUL RELOAD 3 ###################"
|
||||
|
||||
echo "Launching"
|
||||
$pm2 start graceful-exit-send.js -i 2 --name="graceful3" -o "grace3.log" -e "grace-err3.log"
|
||||
should 'should start processes' 'online' 2
|
||||
|
||||
OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"`
|
||||
cat /dev/null > $OUT_LOG
|
||||
|
||||
#### Graceful reload name
|
||||
$pm2 gracefulReload graceful3
|
||||
|
||||
OUT=`grep "Finished closing connections" "$OUT_LOG" | wc -l`
|
||||
[ $OUT -eq 1 ] || fail "Process that sends 'online' not restarted gracefuly"
|
||||
success "Process that sends 'online' restarted gracefuly"
|
||||
@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
cd $file_path
|
||||
|
||||
echo -e "\033[1mRunning Interaction tests:\033[0m"
|
||||
|
||||
$pm2 interact stop
|
||||
|
||||
$pm2 interact
|
||||
|
||||
$pm2 interact XXX2 XXX3 homeloc
|
||||
|
||||
$pm2 updatePM2
|
||||
|
||||
$pm2 interact stop
|
||||
|
||||
$pm2 interact
|
||||
|
||||
$pm2 interact info
|
||||
|
||||
$pm2 interact info | grep "XXX2"
|
||||
spec "Should have XXX2 has public key"
|
||||
|
||||
$pm2 interact info | grep "XXX3"
|
||||
spec "Should have XXX3 has public key"
|
||||
|
||||
$pm2 list
|
||||
|
||||
$pm2 interact stop
|
||||
$pm2 kill
|
||||
@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/../include.sh"
|
||||
cd $file_path
|
||||
|
||||
echo -e "\033[1mRunning tests for json files :\033[0m"
|
||||
|
||||
$pm2 start echo.js --name zero -f
|
||||
$pm2 start echo.js --name one -f
|
||||
$pm2 start echo.js --name two -f
|
||||
should 'should have 3 processes online' 'online' 3
|
||||
$pm2 stop 0
|
||||
$pm2 stop 2
|
||||
$pm2 start echo.js --name three -f
|
||||
$pm2 ls
|
||||
should 'should have 2 processes online' 'online' 2
|
||||
should 'should have 2 processes stopped' 'stopped' 2
|
||||
@ -1,3 +1,10 @@
|
||||
# docker build -t pm2-test -f test/Dockerfile .
|
||||
docker run -v `pwd`:/var/pm2 pm2-test mocha ./test/programmatic/api.mocha.js
|
||||
docker run -v `pwd`:/var/pm2 pm2-test mocha ./test/programmatic/client.mocha.js
|
||||
set -e
|
||||
|
||||
docker build -t pm2-test -f test/Dockerfile .
|
||||
|
||||
JOBS=2
|
||||
OPTS="--jobs $JOBS --joblog joblog-X docker run -v `pwd`:/var/pm2 pm2-test"
|
||||
|
||||
ls test/e2e/cli/* | parallel $OPTS bash
|
||||
|
||||
#ls test/e2e/binaries/* test/e2e/modules/* test/e2e/internal/* test/e2e/process-file/* test/e2e/cli/* test/e2e/logs/* | parallel $OPTS bash
|
||||
|
||||
144
test/e2e.sh
Normal file
144
test/e2e.sh
Normal file
@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/e2e/include.sh"
|
||||
|
||||
# Abort script at first error
|
||||
set -e
|
||||
set -o verbose
|
||||
|
||||
# MODULES
|
||||
bash ./test/e2e/modules/get-set.sh
|
||||
spec "Configuration system working"
|
||||
bash ./test/e2e/modules/module.sh
|
||||
spec "module system"
|
||||
bash ./test/e2e/modules/module-safeguard.sh
|
||||
spec "module safeguard system (--safe)"
|
||||
|
||||
# CLI
|
||||
bash ./test/e2e/cli/reload.sh
|
||||
spec "Reload"
|
||||
bash ./test/e2e/cli/start-app.sh
|
||||
spec "Command line passing"
|
||||
bash ./test/e2e/cli/operate-regex.sh
|
||||
spec "Operate process that match regex"
|
||||
bash ./test/e2e/cli/interpreter.sh
|
||||
spec "Javascript transpilers tests"
|
||||
bash ./test/e2e/cli/app-configuration.sh
|
||||
spec "App configuration"
|
||||
bash ./test/e2e/cli/binary.sh
|
||||
spec "binary test"
|
||||
bash ./test/e2e/cli/startOrX.sh
|
||||
spec "startOrX commands"
|
||||
bash ./test/e2e/cli/reset.sh
|
||||
spec "Reset meta"
|
||||
bash ./test/e2e/cli/env-refresh.sh
|
||||
spec "Environment refresh on restart"
|
||||
bash ./test/e2e/cli/extra-lang.sh
|
||||
spec "Various programming languages checks (Python, PHP)"
|
||||
bash ./test/e2e/cli/multiparam.sh
|
||||
spec "Multiparam process management"
|
||||
bash ./test/e2e/cli/smart-start.sh
|
||||
spec "smart start test"
|
||||
bash ./test/e2e/cli/args.sh
|
||||
spec "check arguments passing"
|
||||
bash ./test/e2e/cli/attach.sh
|
||||
spec "pm2 attach method"
|
||||
bash ./test/e2e/cli/serve.sh
|
||||
spec "pm2 serve CLI method"
|
||||
bash ./test/e2e/cli/monit.sh
|
||||
spec "km selective monitoring "
|
||||
bash ./test/e2e/cli/cli-actions-1.sh
|
||||
spec "CLI basic test"
|
||||
bash ./test/e2e/cli/cli-actions-2.sh
|
||||
spec "Second hard cli tests"
|
||||
bash ./test/e2e/cli/dump.sh
|
||||
spec "dump test"
|
||||
bash ./test/e2e/cli/resurrect.sh
|
||||
spec "resurrect test"
|
||||
bash ./test/e2e/cli/mjs.sh
|
||||
spec "Test import syntax"
|
||||
bash ./test/e2e/cli/watch.sh
|
||||
spec "watch system tests"
|
||||
bash ./test/e2e/cli/right-exit-code.sh
|
||||
spec "Verification exit code"
|
||||
bash ./test/e2e/cli/harmony.sh
|
||||
spec "Harmony test"
|
||||
bash ./test/e2e/cli/fork.sh
|
||||
spec "Fork system working"
|
||||
bash ./test/e2e/cli/piped-config.sh
|
||||
spec "Piped JSON file test"
|
||||
|
||||
# PROCESS FILES
|
||||
bash ./test/e2e/process-file/json-file.sh
|
||||
spec "JSON file test"
|
||||
bash ./test/e2e/process-file/yaml-configuration.sh
|
||||
spec "YAML configuration support"
|
||||
bash ./test/e2e/process-file/json-reload.sh
|
||||
spec "JSON reload test"
|
||||
bash ./test/e2e/process-file/homogen-json-action.sh
|
||||
spec "Homogen json actions"
|
||||
bash ./test/e2e/process-file/app-config-update.sh
|
||||
spec "CLI/JSON argument reload"
|
||||
bash ./test/e2e/process-file/js-configuration.sh
|
||||
spec "js configuration support"
|
||||
|
||||
# BINARIES
|
||||
bash ./test/e2e/binaries/pm2-dev.sh
|
||||
spec "pm2-dev"
|
||||
bash ./test/e2e/binaries/pm2-runtime.sh
|
||||
spec "pm2-runtime"
|
||||
|
||||
# INTERNALS
|
||||
bash ./test/e2e/internals/wait-ready-event.sh
|
||||
spec "Wait for application ready event"
|
||||
bash ./test/e2e/internals/daemon-paths-override.sh
|
||||
spec "Override daemon configuration paths"
|
||||
bash ./test/e2e/internals/source_map.sh
|
||||
spec "Source map resolution on exception"
|
||||
bash ./test/e2e/internals/wrapped-fork.sh
|
||||
spec "wrapped fork"
|
||||
bash ./test/e2e/internals/infinite-loop.sh
|
||||
spec "Infinite loop stop"
|
||||
bash ./test/e2e/internals/options-via-env.sh
|
||||
spec "set option via environment"
|
||||
bash ./test/e2e/internals/promise.sh
|
||||
spec "Promise warning message tests"
|
||||
bash ./test/e2e/internals/increment-var.sh
|
||||
spec "Increment env variables"
|
||||
bash ./test/e2e/internals/start-consistency.sh
|
||||
spec "Consistency between a JSON an CLI start"
|
||||
|
||||
# MISC
|
||||
bash ./test/e2e/misc/inside-pm2.sh
|
||||
spec "Starting a process inside a PM2 process"
|
||||
bash ./test/e2e/misc/vizion.sh
|
||||
spec "vizion features (versioning control)"
|
||||
bash ./test/e2e/misc/misc.sh
|
||||
spec "MISC features"
|
||||
bash ./test/e2e/misc/versioning-cmd.sh
|
||||
spec "versioning system tests"
|
||||
bash ./test/e2e/misc/instance-number.sh
|
||||
spec "Negative instance number spawn one worker"
|
||||
bash ./test/e2e/misc/startup.sh
|
||||
spec "upstart startup test"
|
||||
bash ./test/e2e/misc/nvm-node-version.sh
|
||||
spec "NVM node version setting"
|
||||
bash ./test/e2e/misc/cron-system.sh
|
||||
spec "Cron system tests"
|
||||
|
||||
# LOGS
|
||||
bash ./test/e2e/logs/log-timestamp.sh
|
||||
spec "timestamp prefix of pm2.log"
|
||||
bash ./test/e2e/logs/log-custom.sh
|
||||
spec "Custom log timestamp"
|
||||
bash ./test/e2e/logs/log-reload.sh
|
||||
spec "Log reload"
|
||||
bash ./test/e2e/logs/log-entire.sh
|
||||
spec "merge stdout && stderr"
|
||||
bash ./test/e2e/logs/log-null.sh
|
||||
spec "Logging path set to null"
|
||||
bash ./test/e2e/logs/log-json.sh
|
||||
spec "Logging directly to file in json"
|
||||
|
||||
$pm2 kill
|
||||
@ -1,9 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
pm2dev="`type -P node` `pwd`/bin/pm2-dev"
|
||||
|
||||
pm2_path=`pwd`/bin/pm2-dev
|
||||
|
||||
if [ ! -f $pm2_path ];
|
||||
then
|
||||
pm2_path=`pwd`/../bin/pm2-dev
|
||||
if [ ! -f $pm2_path ];
|
||||
then
|
||||
pm2_path=`pwd`/../../bin/pm2-dev
|
||||
fi
|
||||
fi
|
||||
|
||||
pm2dev="`type -P node` $pm2_path"
|
||||
|
||||
export PM2_HOME=$HOME'/.pm2-dev'
|
||||
|
||||
@ -1,9 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
pm2_runtime="`type -P node` `pwd`/bin/pm2-runtime"
|
||||
|
||||
pm2_path=`pwd`/bin/pm2-runtime
|
||||
|
||||
if [ ! -f $pm2_path ];
|
||||
then
|
||||
pm2_path=`pwd`/../bin/pm2-runtime
|
||||
if [ ! -f $pm2_path ];
|
||||
then
|
||||
pm2_path=`pwd`/../../bin/pm2-runtime
|
||||
fi
|
||||
fi
|
||||
|
||||
pm2_runtime="`type -P node` $pm2_path"
|
||||
|
||||
export PM2_RUNTIME_DEBUG='true'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
echo -e "\033[1mRunning tests:\033[0m"
|
||||
|
||||
@ -59,7 +59,6 @@ exists 'probe test-probe exist' "test-probe"
|
||||
exists 'probe Event Loop Latency exist' "Loop delay"
|
||||
|
||||
exists 'probe Event Loop Latency default value' "agg_type: 'avg'"
|
||||
exists 'probe Event Loop Latency default value' "alert: {}"
|
||||
|
||||
# Set new value for alert probe
|
||||
# $pm2 set probe-test.probes.Event\ Loop\ Latency.value 25
|
||||
@ -68,12 +67,5 @@ exists 'probe Event Loop Latency default value' "alert: {}"
|
||||
# exists 'probe Event Loop Latency alerted' "alert: { cmp: '>', value: 25, mode: 'threshold'"
|
||||
|
||||
# Override value for test-probe
|
||||
$pm2 set probe-test.probes.test-probe.value 30
|
||||
sleep 2
|
||||
|
||||
exists 'probe Event Loop Latency alerted' "value: 30"
|
||||
|
||||
$pm2 restart all
|
||||
sleep 1
|
||||
|
||||
exists 'probe Event Loop Latency alerted' "value: 30"
|
||||
# $pm2 set probe-test.probes.test-probe.value 30
|
||||
# sleep 1
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
cd $file_path/args
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
echo -e "\033[1mRunning tests:\033[0m"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
echo -e "\033[1mRunning tests:\033[0m"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
cd $file_path
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
|
||||
cd $file_path
|
||||
|
||||
############# TEST
|
||||
############# Start / Stop / Restart
|
||||
|
||||
echo -e "\033[1mRunning tests:\033[0m"
|
||||
|
||||
@ -17,7 +17,8 @@ spec "Should stop an app by script.js"
|
||||
$pm2 restart echo.js
|
||||
spec "Should restart an app by script.js (TRANSITIONAL STATE)"
|
||||
|
||||
###############
|
||||
############### Start edge case
|
||||
|
||||
$pm2 delete all
|
||||
|
||||
echo "Start application with filename starting with a numeric"
|
||||
@ -28,28 +29,29 @@ should 'should app be stopped' 'stopped' 1
|
||||
$pm2 restart 001-test
|
||||
should 'should app be online once restart called' 'online' 1
|
||||
|
||||
$pm2 delete all
|
||||
|
||||
############## PID
|
||||
|
||||
$pm2 delete all
|
||||
$pm2 start 001-test.js --name "test"
|
||||
should 'should app be online' 'online' 1
|
||||
$pm2 pid > /tmp/pid-tmp
|
||||
$pm2 pid test
|
||||
$pm2 delete all
|
||||
|
||||
###############
|
||||
|
||||
$pm2 delete all
|
||||
echo "Start application with filename starting with a numeric"
|
||||
$pm2 start throw-string.js -l err-string.log --merge-logs --no-automation
|
||||
>err-string.log
|
||||
sleep 2
|
||||
sleep 1
|
||||
grep 'throw-string.js' err-string.log
|
||||
spec "Should have written raw stack when throwing a string"
|
||||
|
||||
$pm2 delete all
|
||||
|
||||
####
|
||||
|
||||
$pm2 delete all
|
||||
|
||||
$pm2 start echo.js --name gege
|
||||
should 'should app be online' 'online' 1
|
||||
$pm2 stop gege
|
||||
@ -78,7 +80,7 @@ $pm2 start echo.js
|
||||
ispec "Should not re start app"
|
||||
|
||||
########### DELETED STUFF BY ID
|
||||
$pm2 kill
|
||||
$pm2 delete all
|
||||
|
||||
$pm2 start echo.js
|
||||
$pm2 delete 0
|
||||
@ -100,7 +102,7 @@ $pm2 list
|
||||
should 'should has been deleted process by script' "name: 'echo'" 0
|
||||
|
||||
######## Actions on app name as number (#1937)
|
||||
$pm2 kill
|
||||
$pm2 delete all
|
||||
$pm2 start echo.js --name "455"
|
||||
should 'should restart processes' 'restart_time: 0' 1
|
||||
$pm2 restart 455
|
||||
@ -113,9 +115,9 @@ $pm2 delete 455
|
||||
should 'should has been deleted process by id' "name: '455'" 0
|
||||
|
||||
########### OPTIONS OUTPUT FILES
|
||||
$pm2 kill
|
||||
$pm2 delete all
|
||||
|
||||
$pm2 start echo.js -o outech.log -e errech.log --name gmail -i 10
|
||||
$pm2 start echo.js -o outech.log -e errech.log --name gmail -i 1
|
||||
sleep 1
|
||||
cat outech-0.log > /dev/null
|
||||
spec "file outech-0.log exist"
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
cd $file_path
|
||||
|
||||
$pm2 start echo.js -i 4
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC=$(cd $(dirname "$0"); pwd)
|
||||
source "${SRC}/include.sh"
|
||||
source "${SRC}/../include.sh"
|
||||
cd $file_path
|
||||
|
||||
echo -e "\033[1mENV REFRESH\033[0m"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user