A lightweight CLI tool that connects to a remote server over SSH and executes PM2 process manager commands.

Initial Commit

+1079
.github/assets/preview.png

This is a binary file and will not be displayed.

+1
.gitignore
··· 1 + target/
+132
CODE_OF_CONDUCT.md
··· 1 + # Contributor Covenant Code of Conduct 2 + 3 + ## Our Pledge 4 + 5 + We as members, contributors, and leaders pledge to make participation in our 6 + community a harassment-free experience for everyone, regardless of age, body 7 + size, visible or invisible disability, ethnicity, sex characteristics, gender 8 + identity and expression, level of experience, education, socio-economic status, 9 + nationality, personal appearance, race, caste, color, religion, or sexual 10 + identity and orientation. 11 + 12 + We pledge to act and interact in ways that contribute to an open, welcoming, 13 + diverse, inclusive, and healthy community. 14 + 15 + ## Our Standards 16 + 17 + Examples of behavior that contributes to a positive environment for our 18 + community include: 19 + 20 + - Demonstrating empathy and kindness toward other people 21 + - Being respectful of differing opinions, viewpoints, and experiences 22 + - Giving and gracefully accepting constructive feedback 23 + - Accepting responsibility and apologizing to those affected by our mistakes, 24 + and learning from the experience 25 + - Focusing on what is best not just for us as individuals, but for the overall 26 + community 27 + 28 + Examples of unacceptable behavior include: 29 + 30 + - The use of sexualized language or imagery, and sexual attention or advances of 31 + any kind 32 + - Trolling, insulting or derogatory comments, and personal or political attacks 33 + - Public or private harassment 34 + - Publishing others' private information, such as a physical or email address, 35 + without their explicit permission 36 + - Other conduct which could reasonably be considered inappropriate in a 37 + professional setting 38 + 39 + ## Enforcement Responsibilities 40 + 41 + Community leaders are responsible for clarifying and enforcing our standards of 42 + acceptable behavior and will take appropriate and fair corrective action in 43 + response to any behavior that they deem inappropriate, threatening, offensive, 44 + or harmful. 45 + 46 + Community leaders have the right and responsibility to remove, edit, or reject 47 + comments, commits, code, wiki edits, issues, and other contributions that are 48 + not aligned to this Code of Conduct, and will communicate reasons for moderation 49 + decisions when appropriate. 50 + 51 + ## Scope 52 + 53 + This Code of Conduct applies within all community spaces, and also applies when 54 + an individual is officially representing the community in public spaces. 55 + Examples of representing our community include using an official e-mail address, 56 + posting via an official social media account, or acting as an appointed 57 + representative at an online or offline event. 58 + 59 + ## Enforcement 60 + 61 + Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 + reported to the community leaders responsible for enforcement at 63 + [GitHub Issues](https://github.com/tsirysndr/pm22/issues). All 64 + complaints will be reviewed and investigated promptly and fairly. 65 + 66 + All community leaders are obligated to respect the privacy and security of the 67 + reporter of any incident. 68 + 69 + ## Enforcement Guidelines 70 + 71 + Community leaders will follow these Community Impact Guidelines in determining 72 + the consequences for any action they deem in violation of this Code of Conduct: 73 + 74 + ### 1. Correction 75 + 76 + **Community Impact**: Use of inappropriate language or other behavior deemed 77 + unprofessional or unwelcome in the community. 78 + 79 + **Consequence**: A private, written warning from community leaders, providing 80 + clarity around the nature of the violation and an explanation of why the 81 + behavior was inappropriate. A public apology may be requested. 82 + 83 + ### 2. Warning 84 + 85 + **Community Impact**: A violation through a single incident or series of 86 + actions. 87 + 88 + **Consequence**: A warning with consequences for continued behavior. No 89 + interaction with the people involved, including unsolicited interaction with 90 + those enforcing the Code of Conduct, for a specified period of time. This 91 + includes avoiding interactions in community spaces as well as external channels 92 + like social media. Violating these terms may lead to a temporary or permanent 93 + ban. 94 + 95 + ### 3. Temporary Ban 96 + 97 + **Community Impact**: A serious violation of community standards, including 98 + sustained inappropriate behavior. 99 + 100 + **Consequence**: A temporary ban from any sort of interaction or public 101 + communication with the community for a specified period of time. No public or 102 + private interaction with the people involved, including unsolicited interaction 103 + with those enforcing the Code of Conduct, is allowed during this period. 104 + Violating these terms may lead to a permanent ban. 105 + 106 + ### 4. Permanent Ban 107 + 108 + **Community Impact**: Demonstrating a pattern of violation of community 109 + standards, including sustained inappropriate behavior, harassment of an 110 + individual, or aggression toward or disparagement of classes of individuals. 111 + 112 + **Consequence**: A permanent ban from any sort of public interaction within the 113 + community. 114 + 115 + ## Attribution 116 + 117 + This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 + version 2.1, available at 119 + [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 + 121 + Community Impact Guidelines were inspired by 122 + [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 + 124 + For answers to common questions about this code of conduct, see the FAQ at 125 + [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 + [https://www.contributor-covenant.org/translations][translations]. 127 + 128 + [homepage]: https://www.contributor-covenant.org 129 + [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 + [Mozilla CoC]: https://github.com/mozilla/diversity 131 + [FAQ]: https://www.contributor-covenant.org/faq 132 + [translations]: https://www.contributor-covenant.org/translations
+70
CONTRIBUTING.md
··· 1 + # Contributing Guidelines 2 + 3 + Thank you for your interest in contributing to our project. Whether it's a bug 4 + report, new feature, correction, or additional documentation, we greatly value 5 + feedback and contributions from our community. 6 + 7 + Please read through this document before submitting any issues or pull requests 8 + to ensure we have all the necessary information to effectively respond to your 9 + bug report or contribution. 10 + 11 + ## Reporting Bugs/Feature Requests 12 + 13 + We welcome you to use the GitHub issue tracker to report bugs or suggest 14 + features. 15 + 16 + When filing an issue, please check existing open, or recently closed, issues to 17 + make sure somebody else hasn't already reported the issue. Please try to include 18 + as much information as you can. Details like these are incredibly useful: 19 + 20 + - A reproducible test case or series of steps 21 + - The version of our code being used 22 + - Any modifications you've made relevant to the bug 23 + - Anything unusual about your environment or deployment 24 + 25 + ## Contributing via Pull Requests 26 + 27 + Contributions via pull requests are much appreciated. Before sending us a pull 28 + request, please ensure that: 29 + 30 + 1. You are working against the latest source on the _master_ branch. 31 + 2. You check existing open, and recently merged, pull requests to make sure 32 + someone else hasn't addressed the problem already. 33 + 3. You open an issue to discuss any significant work - we would hate for your 34 + time to be wasted. 35 + 36 + To send us a pull request, please: 37 + 38 + 1. Fork the repository. 39 + 2. Modify the source; please focus on the specific change you are contributing. 40 + If you also reformat all the code, it will be hard for us to focus on your 41 + change. 42 + 3. Ensure local tests pass. 43 + 4. Commit to your fork using clear commit messages. 44 + 5. Send us a pull request, answering any default questions in the pull request 45 + interface. 46 + 6. Pay attention to any automated CI failures reported in the pull request, and 47 + stay involved in the conversation. 48 + 49 + GitHub provides additional document on 50 + [forking a repository](https://help.github.com/articles/fork-a-repo/) and 51 + [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 52 + 53 + ## Finding contributions to work on 54 + 55 + Looking at the existing issues is a great way to find something to contribute 56 + on. As our projects, by default, use the default GitHub issue labels 57 + (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 58 + 'help wanted' issues is a great place to start. 59 + 60 + ## Code of Conduct 61 + 62 + This project has adopted the 63 + [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1, 64 + available at 65 + https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. 66 + 67 + ## Licensing 68 + 69 + See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to 70 + confirm the licensing of your contribution.
+631
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "aho-corasick" 7 + version = "1.1.3" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 + dependencies = [ 11 + "memchr", 12 + ] 13 + 14 + [[package]] 15 + name = "anstream" 16 + version = "0.6.19" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" 19 + dependencies = [ 20 + "anstyle", 21 + "anstyle-parse", 22 + "anstyle-query", 23 + "anstyle-wincon", 24 + "colorchoice", 25 + "is_terminal_polyfill", 26 + "utf8parse", 27 + ] 28 + 29 + [[package]] 30 + name = "anstyle" 31 + version = "1.0.11" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 34 + 35 + [[package]] 36 + name = "anstyle-parse" 37 + version = "0.2.7" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 40 + dependencies = [ 41 + "utf8parse", 42 + ] 43 + 44 + [[package]] 45 + name = "anstyle-query" 46 + version = "1.1.3" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" 49 + dependencies = [ 50 + "windows-sys", 51 + ] 52 + 53 + [[package]] 54 + name = "anstyle-wincon" 55 + version = "3.0.9" 56 + source = "registry+https://github.com/rust-lang/crates.io-index" 57 + checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" 58 + dependencies = [ 59 + "anstyle", 60 + "once_cell_polyfill", 61 + "windows-sys", 62 + ] 63 + 64 + [[package]] 65 + name = "anyhow" 66 + version = "1.0.98" 67 + source = "registry+https://github.com/rust-lang/crates.io-index" 68 + checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 69 + 70 + [[package]] 71 + name = "autocfg" 72 + version = "1.5.0" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 75 + 76 + [[package]] 77 + name = "bitflags" 78 + version = "2.9.1" 79 + source = "registry+https://github.com/rust-lang/crates.io-index" 80 + checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 81 + 82 + [[package]] 83 + name = "cc" 84 + version = "1.2.27" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" 87 + dependencies = [ 88 + "shlex", 89 + ] 90 + 91 + [[package]] 92 + name = "cfg-if" 93 + version = "1.0.1" 94 + source = "registry+https://github.com/rust-lang/crates.io-index" 95 + checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 96 + 97 + [[package]] 98 + name = "clap" 99 + version = "4.5.40" 100 + source = "registry+https://github.com/rust-lang/crates.io-index" 101 + checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" 102 + dependencies = [ 103 + "clap_builder", 104 + ] 105 + 106 + [[package]] 107 + name = "clap_builder" 108 + version = "4.5.40" 109 + source = "registry+https://github.com/rust-lang/crates.io-index" 110 + checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" 111 + dependencies = [ 112 + "anstream", 113 + "anstyle", 114 + "clap_lex", 115 + "strsim", 116 + ] 117 + 118 + [[package]] 119 + name = "clap_lex" 120 + version = "0.7.5" 121 + source = "registry+https://github.com/rust-lang/crates.io-index" 122 + checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 123 + 124 + [[package]] 125 + name = "colorchoice" 126 + version = "1.0.4" 127 + source = "registry+https://github.com/rust-lang/crates.io-index" 128 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 129 + 130 + [[package]] 131 + name = "dirs" 132 + version = "6.0.0" 133 + source = "registry+https://github.com/rust-lang/crates.io-index" 134 + checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 135 + dependencies = [ 136 + "dirs-sys", 137 + ] 138 + 139 + [[package]] 140 + name = "dirs-sys" 141 + version = "0.5.0" 142 + source = "registry+https://github.com/rust-lang/crates.io-index" 143 + checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 144 + dependencies = [ 145 + "libc", 146 + "option-ext", 147 + "redox_users", 148 + "windows-sys", 149 + ] 150 + 151 + [[package]] 152 + name = "env_filter" 153 + version = "0.1.3" 154 + source = "registry+https://github.com/rust-lang/crates.io-index" 155 + checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 156 + dependencies = [ 157 + "log", 158 + "regex", 159 + ] 160 + 161 + [[package]] 162 + name = "env_logger" 163 + version = "0.11.8" 164 + source = "registry+https://github.com/rust-lang/crates.io-index" 165 + checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 166 + dependencies = [ 167 + "anstream", 168 + "anstyle", 169 + "env_filter", 170 + "jiff", 171 + "log", 172 + ] 173 + 174 + [[package]] 175 + name = "getrandom" 176 + version = "0.2.16" 177 + source = "registry+https://github.com/rust-lang/crates.io-index" 178 + checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 179 + dependencies = [ 180 + "cfg-if", 181 + "libc", 182 + "wasi", 183 + ] 184 + 185 + [[package]] 186 + name = "is_terminal_polyfill" 187 + version = "1.70.1" 188 + source = "registry+https://github.com/rust-lang/crates.io-index" 189 + checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 190 + 191 + [[package]] 192 + name = "jiff" 193 + version = "0.2.15" 194 + source = "registry+https://github.com/rust-lang/crates.io-index" 195 + checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" 196 + dependencies = [ 197 + "jiff-static", 198 + "log", 199 + "portable-atomic", 200 + "portable-atomic-util", 201 + "serde", 202 + ] 203 + 204 + [[package]] 205 + name = "jiff-static" 206 + version = "0.2.15" 207 + source = "registry+https://github.com/rust-lang/crates.io-index" 208 + checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" 209 + dependencies = [ 210 + "proc-macro2", 211 + "quote", 212 + "syn", 213 + ] 214 + 215 + [[package]] 216 + name = "libc" 217 + version = "0.2.174" 218 + source = "registry+https://github.com/rust-lang/crates.io-index" 219 + checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 220 + 221 + [[package]] 222 + name = "libredox" 223 + version = "0.1.3" 224 + source = "registry+https://github.com/rust-lang/crates.io-index" 225 + checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 226 + dependencies = [ 227 + "bitflags", 228 + "libc", 229 + ] 230 + 231 + [[package]] 232 + name = "libssh2-sys" 233 + version = "0.3.1" 234 + source = "registry+https://github.com/rust-lang/crates.io-index" 235 + checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" 236 + dependencies = [ 237 + "cc", 238 + "libc", 239 + "libz-sys", 240 + "openssl-sys", 241 + "pkg-config", 242 + "vcpkg", 243 + ] 244 + 245 + [[package]] 246 + name = "libz-sys" 247 + version = "1.1.22" 248 + source = "registry+https://github.com/rust-lang/crates.io-index" 249 + checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" 250 + dependencies = [ 251 + "cc", 252 + "libc", 253 + "pkg-config", 254 + "vcpkg", 255 + ] 256 + 257 + [[package]] 258 + name = "lock_api" 259 + version = "0.4.13" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 262 + dependencies = [ 263 + "autocfg", 264 + "scopeguard", 265 + ] 266 + 267 + [[package]] 268 + name = "log" 269 + version = "0.4.27" 270 + source = "registry+https://github.com/rust-lang/crates.io-index" 271 + checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 272 + 273 + [[package]] 274 + name = "memchr" 275 + version = "2.7.5" 276 + source = "registry+https://github.com/rust-lang/crates.io-index" 277 + checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 278 + 279 + [[package]] 280 + name = "once_cell_polyfill" 281 + version = "1.70.1" 282 + source = "registry+https://github.com/rust-lang/crates.io-index" 283 + checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 284 + 285 + [[package]] 286 + name = "openssl-sys" 287 + version = "0.9.109" 288 + source = "registry+https://github.com/rust-lang/crates.io-index" 289 + checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 290 + dependencies = [ 291 + "cc", 292 + "libc", 293 + "pkg-config", 294 + "vcpkg", 295 + ] 296 + 297 + [[package]] 298 + name = "option-ext" 299 + version = "0.2.0" 300 + source = "registry+https://github.com/rust-lang/crates.io-index" 301 + checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 302 + 303 + [[package]] 304 + name = "owo-colors" 305 + version = "4.2.1" 306 + source = "registry+https://github.com/rust-lang/crates.io-index" 307 + checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" 308 + 309 + [[package]] 310 + name = "parking_lot" 311 + version = "0.12.4" 312 + source = "registry+https://github.com/rust-lang/crates.io-index" 313 + checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 314 + dependencies = [ 315 + "lock_api", 316 + "parking_lot_core", 317 + ] 318 + 319 + [[package]] 320 + name = "parking_lot_core" 321 + version = "0.9.11" 322 + source = "registry+https://github.com/rust-lang/crates.io-index" 323 + checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 324 + dependencies = [ 325 + "cfg-if", 326 + "libc", 327 + "redox_syscall", 328 + "smallvec", 329 + "windows-targets", 330 + ] 331 + 332 + [[package]] 333 + name = "pkg-config" 334 + version = "0.3.32" 335 + source = "registry+https://github.com/rust-lang/crates.io-index" 336 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 337 + 338 + [[package]] 339 + name = "pm22" 340 + version = "0.1.0" 341 + dependencies = [ 342 + "anyhow", 343 + "clap", 344 + "env_logger", 345 + "log", 346 + "owo-colors", 347 + "shell-escape", 348 + "shellexpand", 349 + "ssh2", 350 + ] 351 + 352 + [[package]] 353 + name = "portable-atomic" 354 + version = "1.11.1" 355 + source = "registry+https://github.com/rust-lang/crates.io-index" 356 + checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 357 + 358 + [[package]] 359 + name = "portable-atomic-util" 360 + version = "0.2.4" 361 + source = "registry+https://github.com/rust-lang/crates.io-index" 362 + checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 363 + dependencies = [ 364 + "portable-atomic", 365 + ] 366 + 367 + [[package]] 368 + name = "proc-macro2" 369 + version = "1.0.95" 370 + source = "registry+https://github.com/rust-lang/crates.io-index" 371 + checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 372 + dependencies = [ 373 + "unicode-ident", 374 + ] 375 + 376 + [[package]] 377 + name = "quote" 378 + version = "1.0.40" 379 + source = "registry+https://github.com/rust-lang/crates.io-index" 380 + checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 381 + dependencies = [ 382 + "proc-macro2", 383 + ] 384 + 385 + [[package]] 386 + name = "redox_syscall" 387 + version = "0.5.13" 388 + source = "registry+https://github.com/rust-lang/crates.io-index" 389 + checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" 390 + dependencies = [ 391 + "bitflags", 392 + ] 393 + 394 + [[package]] 395 + name = "redox_users" 396 + version = "0.5.0" 397 + source = "registry+https://github.com/rust-lang/crates.io-index" 398 + checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" 399 + dependencies = [ 400 + "getrandom", 401 + "libredox", 402 + "thiserror", 403 + ] 404 + 405 + [[package]] 406 + name = "regex" 407 + version = "1.11.1" 408 + source = "registry+https://github.com/rust-lang/crates.io-index" 409 + checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 410 + dependencies = [ 411 + "aho-corasick", 412 + "memchr", 413 + "regex-automata", 414 + "regex-syntax", 415 + ] 416 + 417 + [[package]] 418 + name = "regex-automata" 419 + version = "0.4.9" 420 + source = "registry+https://github.com/rust-lang/crates.io-index" 421 + checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 422 + dependencies = [ 423 + "aho-corasick", 424 + "memchr", 425 + "regex-syntax", 426 + ] 427 + 428 + [[package]] 429 + name = "regex-syntax" 430 + version = "0.8.5" 431 + source = "registry+https://github.com/rust-lang/crates.io-index" 432 + checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 433 + 434 + [[package]] 435 + name = "scopeguard" 436 + version = "1.2.0" 437 + source = "registry+https://github.com/rust-lang/crates.io-index" 438 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 439 + 440 + [[package]] 441 + name = "serde" 442 + version = "1.0.219" 443 + source = "registry+https://github.com/rust-lang/crates.io-index" 444 + checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 445 + dependencies = [ 446 + "serde_derive", 447 + ] 448 + 449 + [[package]] 450 + name = "serde_derive" 451 + version = "1.0.219" 452 + source = "registry+https://github.com/rust-lang/crates.io-index" 453 + checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 454 + dependencies = [ 455 + "proc-macro2", 456 + "quote", 457 + "syn", 458 + ] 459 + 460 + [[package]] 461 + name = "shell-escape" 462 + version = "0.1.5" 463 + source = "registry+https://github.com/rust-lang/crates.io-index" 464 + checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" 465 + 466 + [[package]] 467 + name = "shellexpand" 468 + version = "3.1.1" 469 + source = "registry+https://github.com/rust-lang/crates.io-index" 470 + checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" 471 + dependencies = [ 472 + "dirs", 473 + ] 474 + 475 + [[package]] 476 + name = "shlex" 477 + version = "1.3.0" 478 + source = "registry+https://github.com/rust-lang/crates.io-index" 479 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 480 + 481 + [[package]] 482 + name = "smallvec" 483 + version = "1.15.1" 484 + source = "registry+https://github.com/rust-lang/crates.io-index" 485 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 486 + 487 + [[package]] 488 + name = "ssh2" 489 + version = "0.9.5" 490 + source = "registry+https://github.com/rust-lang/crates.io-index" 491 + checksum = "2f84d13b3b8a0d4e91a2629911e951db1bb8671512f5c09d7d4ba34500ba68c8" 492 + dependencies = [ 493 + "bitflags", 494 + "libc", 495 + "libssh2-sys", 496 + "parking_lot", 497 + ] 498 + 499 + [[package]] 500 + name = "strsim" 501 + version = "0.11.1" 502 + source = "registry+https://github.com/rust-lang/crates.io-index" 503 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 504 + 505 + [[package]] 506 + name = "syn" 507 + version = "2.0.104" 508 + source = "registry+https://github.com/rust-lang/crates.io-index" 509 + checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 510 + dependencies = [ 511 + "proc-macro2", 512 + "quote", 513 + "unicode-ident", 514 + ] 515 + 516 + [[package]] 517 + name = "thiserror" 518 + version = "2.0.12" 519 + source = "registry+https://github.com/rust-lang/crates.io-index" 520 + checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 521 + dependencies = [ 522 + "thiserror-impl", 523 + ] 524 + 525 + [[package]] 526 + name = "thiserror-impl" 527 + version = "2.0.12" 528 + source = "registry+https://github.com/rust-lang/crates.io-index" 529 + checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 530 + dependencies = [ 531 + "proc-macro2", 532 + "quote", 533 + "syn", 534 + ] 535 + 536 + [[package]] 537 + name = "unicode-ident" 538 + version = "1.0.18" 539 + source = "registry+https://github.com/rust-lang/crates.io-index" 540 + checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 541 + 542 + [[package]] 543 + name = "utf8parse" 544 + version = "0.2.2" 545 + source = "registry+https://github.com/rust-lang/crates.io-index" 546 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 547 + 548 + [[package]] 549 + name = "vcpkg" 550 + version = "0.2.15" 551 + source = "registry+https://github.com/rust-lang/crates.io-index" 552 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 553 + 554 + [[package]] 555 + name = "wasi" 556 + version = "0.11.1+wasi-snapshot-preview1" 557 + source = "registry+https://github.com/rust-lang/crates.io-index" 558 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 559 + 560 + [[package]] 561 + name = "windows-sys" 562 + version = "0.59.0" 563 + source = "registry+https://github.com/rust-lang/crates.io-index" 564 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 565 + dependencies = [ 566 + "windows-targets", 567 + ] 568 + 569 + [[package]] 570 + name = "windows-targets" 571 + version = "0.52.6" 572 + source = "registry+https://github.com/rust-lang/crates.io-index" 573 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 574 + dependencies = [ 575 + "windows_aarch64_gnullvm", 576 + "windows_aarch64_msvc", 577 + "windows_i686_gnu", 578 + "windows_i686_gnullvm", 579 + "windows_i686_msvc", 580 + "windows_x86_64_gnu", 581 + "windows_x86_64_gnullvm", 582 + "windows_x86_64_msvc", 583 + ] 584 + 585 + [[package]] 586 + name = "windows_aarch64_gnullvm" 587 + version = "0.52.6" 588 + source = "registry+https://github.com/rust-lang/crates.io-index" 589 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 590 + 591 + [[package]] 592 + name = "windows_aarch64_msvc" 593 + version = "0.52.6" 594 + source = "registry+https://github.com/rust-lang/crates.io-index" 595 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 596 + 597 + [[package]] 598 + name = "windows_i686_gnu" 599 + version = "0.52.6" 600 + source = "registry+https://github.com/rust-lang/crates.io-index" 601 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 602 + 603 + [[package]] 604 + name = "windows_i686_gnullvm" 605 + version = "0.52.6" 606 + source = "registry+https://github.com/rust-lang/crates.io-index" 607 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 608 + 609 + [[package]] 610 + name = "windows_i686_msvc" 611 + version = "0.52.6" 612 + source = "registry+https://github.com/rust-lang/crates.io-index" 613 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 614 + 615 + [[package]] 616 + name = "windows_x86_64_gnu" 617 + version = "0.52.6" 618 + source = "registry+https://github.com/rust-lang/crates.io-index" 619 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 620 + 621 + [[package]] 622 + name = "windows_x86_64_gnullvm" 623 + version = "0.52.6" 624 + source = "registry+https://github.com/rust-lang/crates.io-index" 625 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 626 + 627 + [[package]] 628 + name = "windows_x86_64_msvc" 629 + version = "0.52.6" 630 + source = "registry+https://github.com/rust-lang/crates.io-index" 631 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+21
Cargo.toml
··· 1 + [package] 2 + name = "pm22" 3 + version = "0.1.0" 4 + edition = "2024" 5 + description = "A lightweight CLI tool that connects to a remote server over SSH and executes PM2 process manager commands." 6 + license = "MIT" 7 + keywords = ["cli", "ssh", "pm2"] 8 + readme = "README.md" 9 + repository = "https://github.com/tsirysndr/pm22" 10 + categories = ["command-line-utilities"] 11 + authors = ["Tsiry Sandratraina <tsiry.sndr@rocksky.app>"] 12 + 13 + [dependencies] 14 + anyhow = "1.0.98" 15 + clap = "4.5.40" 16 + env_logger = "0.11.8" 17 + log = "0.4.27" 18 + owo-colors = "4.2.1" 19 + shell-escape = "0.1.5" 20 + shellexpand = "3.1.1" 21 + ssh2 = "0.9.5"
+19
LICENSE
··· 1 + Copyright (c) 2025 Tsiry Sandratraina <tsiry.sndr@rocksky.app> 2 + 3 + Permission is hereby granted, free of charge, to any person obtaining a copy 4 + of this software and associated documentation files (the "Software"), to deal 5 + in the Software without restriction, including without limitation the rights 6 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 + copies of the Software, and to permit persons to whom the Software is 8 + furnished to do so, subject to the following conditions: 9 + 10 + The above copyright notice and this permission notice shall be included in all 11 + copies or substantial portions of the Software. 12 + 13 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 + SOFTWARE.
+54
README.md
··· 1 + # PM22 2 + 3 + [![downloads](https://img.shields.io/crates/dr/pm22)](https://crates.io/crates/pm22) 4 + [![crates](https://img.shields.io/crates/v/pm22.svg)](https://crates.io/crates/pm22) 5 + 6 + 7 + 8 + `pm22` is a lightweight CLI tool that connects to a remote server over SSH and executes [PM2](https://pm2.keymetrics.io/) process manager commands. 9 + 10 + Perfect for remotely managing Node.js applications from your terminal. 11 + 12 + ![Preview](https://raw.githubusercontent.com/tsirysndr/pm22/main/.github/assets/preview.png) 13 + 14 + 15 + ## ✨ Features 16 + 17 + - SSH into any server with your private key 18 + - Execute any PM2 command remotely (`start`, `stop`, `restart`, `delete`, `logs`, etc.) 19 + - Supports custom ports and SSH keys 20 + - Optional verbose output with `--verbose` 21 + 22 + ## 🚚 Installation 23 + You can install `pm22` globally using cargo: 24 + 25 + ```bash 26 + cargo install pm22 27 + ``` 28 + 29 + ## 🚀 Usage 30 + 31 + ```bash 32 + pm22 [OPTIONS] <host> <cmd> [args]... 33 + ``` 34 + 35 + ## 🔹 Arguments 36 + 37 + | Name | Description | 38 + | ----------- | ------------------------------------------------------------------- | 39 + | `<host>` | Host to connect to, including username (e.g., user@your.server.com) | 40 + | `<cmd>` | PM2 command to execute (start, restart, status, logs, etc.) | 41 + | `[args]...` | Additional arguments passed to the PM2 command | 42 + 43 + ## 🔹 Options 44 + 45 + | Flag | Description | Default | 46 + | --------------------- | ------------------------------------------- | --------------- | 47 + | `-p`, `--port <port>` | SSH port to connect to | `22` | 48 + | `-k`, `--key <path>` | Path to your SSH private key | `~/.ssh/id_rsa` | 49 + | `-v`, `--verbose` | Enable verbose output for debugging/logging | | 50 + | `-h`, `--help` | Show help information | | 51 + | `-V`, `--version` | Show version information | | 52 + 53 + ## 📄 License 54 + MIT License © 2025 [Tsiry Sandratraina](https://github.com/tsirysndr)
+71
src/main.rs
··· 1 + use crate::pm2::run_pm2_command; 2 + use clap::{Arg, Command as ClapCommand}; 3 + 4 + pub mod pm2; 5 + 6 + fn main() -> Result<(), Box<dyn std::error::Error>> { 7 + let matches = ClapCommand::new("pm22") 8 + .about("Connects to a remote server via SSH and executes PM2 commands") 9 + .version("0.1.0") 10 + .author("Tsiry Sandratraina <tsiry.sndr@rocksky.app>") 11 + .arg( 12 + Arg::new("host") 13 + .required(true) 14 + .help("Host to connect to, with username (e.g., user@host)"), 15 + ) 16 + .arg( 17 + Arg::new("port") 18 + .short('p') 19 + .long("port") 20 + .default_value("22") 21 + .help("Port to connect to (default: 22)"), 22 + ) 23 + .arg( 24 + Arg::new("verbose") 25 + .short('v') 26 + .long("verbose") 27 + .action(clap::ArgAction::SetTrue) 28 + .help("Enable verbose output"), 29 + ) 30 + .arg( 31 + Arg::new("key") 32 + .short('k') 33 + .long("key") 34 + .default_value("~/.ssh/id_rsa") 35 + .help("SSH private key file (default: ~/.ssh/id_rsa)"), 36 + ) 37 + .arg( 38 + Arg::new("cmd") 39 + .required(true) 40 + .help("PM2 Command to run on the remote server, e.g., ps, status, start, stop, restart, delete, logs, etc.") 41 + ) 42 + .arg( 43 + Arg::new("args") 44 + .trailing_var_arg(true) 45 + .allow_hyphen_values(true) 46 + .num_args(0..) 47 + .help("Arguments to pass to pm2 command"), 48 + ) 49 + .get_matches(); 50 + 51 + let verbose = matches.get_flag("verbose"); 52 + init_logger(verbose); 53 + 54 + let host = matches.get_one::<String>("host").unwrap(); 55 + let port = matches.get_one::<String>("port").unwrap(); 56 + let key = matches.get_one::<String>("key").unwrap(); 57 + let cmd = matches.get_one::<String>("cmd").unwrap(); 58 + let args: Vec<&String> = matches 59 + .get_many::<String>("args") 60 + .map(|vals| vals.collect()) 61 + .unwrap_or_default(); 62 + 63 + run_pm2_command(host, port.parse().unwrap_or(22), key, cmd, args)?; 64 + Ok(()) 65 + } 66 + 67 + pub fn init_logger(verbose: bool) { 68 + let level = if verbose { "debug" } else { "warn" }; 69 + unsafe { std::env::set_var("RUST_LOG", level) }; 70 + env_logger::init(); 71 + }
+80
src/pm2.rs
··· 1 + use std::{io::Read, net::TcpStream, path::Path}; 2 + 3 + use anyhow::Error; 4 + use log::info; 5 + use owo_colors::OwoColorize; 6 + use ssh2::Session; 7 + 8 + pub fn run_pm2_command( 9 + host: &str, 10 + port: u32, 11 + key: &str, 12 + cmd: &str, 13 + args: Vec<&String>, 14 + ) -> Result<(), Error> { 15 + let parts: Vec<&str> = host.split('@').collect(); 16 + if parts.len() != 2 { 17 + return Err(Error::msg("Host must be in the format user@host")); 18 + } 19 + let username = parts[0]; 20 + 21 + let host = parts[1]; 22 + 23 + let ssh_url = format!("{}@{}:{}", username, host, port); 24 + info!( 25 + "Connecting to {} using key {} ...", 26 + ssh_url.bright_cyan(), 27 + key.bright_cyan() 28 + ); 29 + 30 + let tcp = TcpStream::connect(format!("{}:{}", host, port))?; 31 + let mut session = Session::new()?; 32 + session.set_tcp_stream(tcp); 33 + session.handshake()?; 34 + 35 + let private_key_path = shellexpand::tilde(key).to_string(); 36 + let private_key = Path::new(&private_key_path); 37 + session.userauth_pubkey_file(username, None, private_key, None)?; 38 + if !session.authenticated() { 39 + return Err(Error::msg("SSH authentication failed")); 40 + } 41 + 42 + info!("Connected to {} successfully!", ssh_url.bright_cyan()); 43 + 44 + let mut channel = session.channel_session()?; 45 + let command = format!( 46 + "NO_NEOFETCH=1 bash -lic 'pm2 {} {}'", 47 + cmd, 48 + args.iter() 49 + .map(|s| s.as_str()) 50 + .collect::<Vec<&str>>() 51 + .join(" ") 52 + ); 53 + 54 + info!("Executing command: {}", command.bright_yellow()); 55 + 56 + channel.request_pty("xterm", None, None)?; 57 + channel.exec(&command)?; 58 + 59 + let mut buffer = [0; 1024]; 60 + 61 + // Stream stdout in real-time 62 + loop { 63 + match channel.read(&mut buffer) { 64 + Ok(0) => break, // EOF, command finished 65 + Ok(n) => { 66 + let output = String::from_utf8_lossy(&buffer[..n]); 67 + print!("{}", output); // Real-time output 68 + } 69 + Err(e) => { 70 + eprintln!("Error reading from channel: {}", e); 71 + break; 72 + } 73 + } 74 + } 75 + 76 + channel.wait_close()?; 77 + info!("Exit status: {}", channel.exit_status()?); 78 + 79 + Ok(()) 80 + }