├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── lint.yml ├── .gitignore ├── .golangci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── build-all.sh ├── cmd_connect.go ├── cmd_echo.go ├── cmd_kill.go ├── cmd_quit.go ├── cmd_restart.go ├── cmd_run.go ├── cmd_status.go ├── cmd_stop.go ├── dialer.go ├── go.mod ├── go.sum ├── lefthook.yml ├── logo-dark.svg ├── logo-sign-only.svg ├── logo.svg ├── main.go ├── packaging ├── rubygems │ ├── .gitignore │ ├── .rspec │ ├── Gemfile │ ├── Gemfile.lock │ ├── LICENSE.txt │ ├── NOTICE │ ├── README.md │ ├── RELEASING.md │ ├── Rakefile │ ├── bin │ │ └── overmind │ ├── gemfiles │ │ └── rubocop.gemfile │ ├── lib │ │ ├── overmind.rb │ │ └── overmind │ │ │ ├── cli.rb │ │ │ └── version.rb │ ├── libexec │ │ └── .gitkeep │ ├── overmind.gemspec │ ├── rakelib │ │ └── build_gem_with_dependencies.rake │ ├── spec │ │ ├── dummy │ │ │ └── server.rb │ │ ├── integration │ │ │ └── overmind_spec.rb │ │ └── spec_helper.rb │ └── support │ │ └── downloader │ │ ├── base_downloader.rb │ │ ├── overmind_downloader.rb │ │ └── tmux_downloader.rb └── tmux │ ├── NOTICE │ ├── README.md │ ├── data │ ├── .gitkeep │ └── Dockerfile │ └── scripts │ └── generate_tmux.sh ├── start ├── command.go ├── command_center.go ├── handler.go ├── multi_output.go ├── process.go ├── procfile.go └── tmux.go └── utils └── utils.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | target-branch: "dependencies" 7 | schedule: 8 | interval: "weekly" 9 | day: "monday" 10 | time: "06:00" # 6:00 UTC 11 | commit-message: 12 | prefix: "deps" 13 | assignees: 14 | - "envek" 15 | - "mrexox" 16 | reviewers: 17 | - "envek" 18 | - "mrexox" 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | go-version: ["1.18.x", "1.19.x", "1.20.x", "1.21.x"] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - uses: actions/setup-go@v3 17 | with: 18 | go-version: ${{ matrix.go-version }} 19 | - name: Cache mods 20 | uses: actions/cache@v3 21 | with: 22 | path: | 23 | ~/.cache/go-build 24 | ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} 26 | - name: Download mods 27 | run: go mod download 28 | - name: Build 29 | run: go build 30 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | go-version: ["1.21.x"] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - uses: actions/setup-go@v3 17 | with: 18 | go-version: ${{ matrix.go-version }} 19 | - name: Cache mods 20 | uses: actions/cache@v3 21 | with: 22 | path: | 23 | ~/.cache/go-build 24 | ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} 26 | - name: Download mods 27 | run: go mod download 28 | - name: Lint 29 | uses: golangci/golangci-lint-action@v3 30 | with: 31 | version: v1.54.1 32 | args: --timeout 5m0s 33 | skip-cache: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compilled binary 2 | overmind 3 | 4 | # Test Procfile 5 | Procfile 6 | Procfile.* 7 | 8 | # Test env 9 | .env 10 | .env.* 11 | .overmind.env 12 | 13 | .overmind.sock 14 | 15 | tmp/ 16 | .tmp/ 17 | dist/ 18 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-dirs: 3 | - .tmp 4 | - vendor 5 | 6 | linters: 7 | disable-all: true 8 | enable: 9 | # - errcheck 10 | - gocritic 11 | - goconst 12 | - goimports 13 | - gosimple 14 | - govet 15 | - ineffassign 16 | - staticcheck 17 | - stylecheck 18 | - typecheck 19 | - unused 20 | 21 | linters-settings: 22 | govet: 23 | # report about shadowed variables 24 | check-shadowing: true 25 | 26 | issues: 27 | exclude-rules: 28 | - linters: [stylecheck] 29 | text: "ST1005:" 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.12" 4 | - "1.13" 5 | - "1.14" 6 | env: 7 | - GO111MODULE=on 8 | install: 9 | - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.20.0 10 | script: 11 | - go mod download 12 | - golangci-lint run 13 | - go build 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sergey Alexandrovich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | Overmind 5 | 6 |

7 | 8 |

9 | Release 10 | GH Build 11 | GH Lint 12 |

13 | 14 | --- 15 | 16 | Overmind is a process manager for Procfile-based applications and [tmux](https://tmux.github.io/). With Overmind, you can easily run several processes from your `Procfile` in a single terminal. 17 | 18 | Procfile is a simple format to specify types of processes your application provides (such as web application server, background queue process, front-end builder) and commands to run those processes. It can significantly simplify process management for developers and is used by popular hosting platforms, such as Heroku and Deis. You can learn more about the `Procfile` format [here](https://devcenter.heroku.com/articles/procfile). 19 | 20 | There are some good Procfile-based process management tools, including [foreman](https://github.com/ddollar/foreman) by David Dollar, which started it all. The problem with most of those tools is that processes you want to manage start to think they are logging their output into a file, and that can lead to all sorts of problems: severe lagging, and losing or breaking colored output. Tools can also add vanity information (unneeded timestamps in logs). Overmind was created to fix those problems once and for all. 21 | 22 | See this article for a good intro and all the juicy details! [Introducing 23 | Overmind and Hivemind](https://evilmartians.com/chronicles/introducing-overmind-and-hivemind) 24 | 25 | 26 | 27 | 31 | Sponsored by Evil Martians 37 | 38 | 39 | 40 | ## Overmind features 41 | 42 | You may know several Procfile process management tools, but Overmind has some unique, _extraterrestrial_ powers others don't: 43 | 44 | * Overmind starts processes in a tmux session, so you can easily connect to any process and gain control over it; 45 | * Overmind can restart a single process on the fly — you don't need to restart the whole stack; 46 | * Overmind allows a specified process to die without interrupting all of the other ones; 47 | * Overmind can restart specified processes automatically when they die; 48 | * Overmind uses tmux's control mode to capture process output — so it won't be clipped, delayed, and it won't break colored output; 49 | * Overmind can read environment variables from a file and use them as parameters so that you can configure Overmind behavior globally and/or per directory. 50 | 51 | **If a lot of those features seem like overkill for you, especially the tmux integration, you should take a look at Overmind's little sister — [Hivemind](https://github.com/DarthSim/hivemind)!** 52 | 53 | ![Overmind screenshot](http://i.imgur.com/lfrFKMf.png) 54 | 55 | ## Installation 56 | 57 | **Note:** At the moment, Overmind supports Linux, *BSD, and macOS only. 58 | 59 | Overmind works with [tmux](https://tmux.github.io/), so you need to install it first: 60 | 61 | ```bash 62 | # on macOS (with homebrew) 63 | $ brew install tmux 64 | 65 | # on Ubuntu 66 | $ apt-get install tmux 67 | ``` 68 | 69 | **Note:** You can find installation manuals for other systems here: https://github.com/tmux/tmux 70 | 71 | There are three ways to install Overmind: 72 | 73 | ### With Homebrew (macOS) 74 | 75 | ```bash 76 | brew install overmind 77 | ``` 78 | 79 | ### With Ruby 80 | 81 | ```bash 82 | gem install overmind 83 | ``` 84 | 85 | You can read about installing on Ruby On Rails [here] (https://github.com/DarthSim/overmind/blob/master/packaging/rubygems/README.md) 86 | 87 | ### Download the latest Overmind release binary 88 | 89 | You can download the latest release [here](https://github.com/DarthSim/overmind/releases/latest). 90 | 91 | ### Build Overmind from source 92 | 93 | You need Go 1.21 or later to build the project. 94 | 95 | ```bash 96 | $ go install github.com/DarthSim/overmind/v2@latest 97 | ``` 98 | 99 | The Overmind binary will be installed to `$(go env GOPATH)/bin`. Make sure that you added it to your `PATH`. 100 | 101 | **Note:** You can update Overmind the same way. 102 | 103 | ## Usage 104 | 105 | **In short:** You can get help by running `overmind -h` and `overmind help [command]`. 106 | 107 | ### Running processes 108 | 109 | Overmind reads the list of processes you want to manage from a file named `Procfile`. It may look like this: 110 | 111 | ```Procfile 112 | web: bin/rails server 113 | worker: bundle exec sidekiq 114 | assets: gulp watch 115 | ``` 116 | 117 | To get started, you just need to run Overmind from your working directory containing a `Procfile`: 118 | 119 | ```bash 120 | $ overmind start 121 | ``` 122 | 123 | You can also use the short alias: 124 | 125 | ```bash 126 | $ overmind s 127 | ``` 128 | 129 | #### Specifying a Procfile 130 | 131 | If a `Procfile` isn't located in your working directory, you can specify the exact path: 132 | 133 | ```bash 134 | $ overmind start -f path/to/your/Procfile 135 | $ OVERMIND_PROCFILE=path/to/your/Procfile overmind start 136 | ``` 137 | 138 | #### Specifying the ports 139 | 140 | Overmind sets the environment variable `PORT` for each process in your Procfile so that you can do things like this: 141 | 142 | ```Procfile 143 | web: bin/rails server -p $PORT 144 | ``` 145 | 146 | Overmind assigns the port base (5000 by default) to `PORT` for the first process and increases `PORT` by port step (100 by default) for each subsequent one. You can specify the port base and port step like this: 147 | 148 | ```bash 149 | $ overmind start -p 3000 -P 10 150 | $ OVERMIND_PORT=3000 OVERMIND_PORT_STEP=10 overmind start 151 | ``` 152 | 153 | #### Disabling `PORT` 154 | 155 | If you don't want Overmind to set the `PORT` variable, you can disable it: 156 | 157 | ```bash 158 | $ overmind start -N 159 | $ OVERMIND_NO_PORT=1 overmind start 160 | ``` 161 | 162 | #### Referencing the ports of other processes 163 | 164 | If overmind is specifying a port, each other process will be provided with environment variables that include them. Those variables will be have the format `OVERMIND_PROCESS__PORT` where "name" is the name of the process from the procfile, with any characters that are invalid in an environment variable replaced with `_`. 165 | 166 | If processes are scaled, those port names will be numbered as well, based on the scaling of the process. 167 | 168 | ```Procfile 169 | web: bin/rails server -p $PORT 170 | proxy: ngrok http --subdomain overmind $OVERMIND_PROCESS_web_PORT 171 | ``` 172 | 173 | #### Running only the specified processes 174 | 175 | You can specify the names of processes you want to run: 176 | 177 | ```bash 178 | $ overmind start -l web,sidekiq 179 | $ OVERMIND_PROCESSES=web,sidekiq overmind start 180 | ``` 181 | 182 | #### Not running the specified processes 183 | 184 | Similar to the above, if there are some processes in the Procfile that you do not want to run: 185 | 186 | ```bash 187 | $ overmind start -x web,sidekiq 188 | $ OVERMIND_IGNORED_PROCESSES=web,sidekiq overmind start 189 | ``` 190 | 191 | This takes precedence over the previous `-l` flag. i.e. if you: 192 | 193 | ```bash 194 | $ overmind start -l web -x web 195 | $ OVERMIND_IGNORED_PROCESSES=web OVERMIND_PROCESSES=web overmind start 196 | ``` 197 | 198 | Nothing will start. 199 | 200 | #### Scaling processes (formation) 201 | 202 | By default, Overmind starts one instance of each process, but you can set the number of each process instances to run: 203 | 204 | ```bash 205 | $ overmind start -m web=2,worker=5 206 | $ OVERMIND_FORMATION=web=2,worker=5 overmind start 207 | ``` 208 | 209 | There is a special name `all` that you can use to scale all processes at once: 210 | 211 | ```bash 212 | $ overmind start -m all=2,worker=5 213 | $ OVERMIND_FORMATION=all=2,worker=5 overmind start 214 | ``` 215 | 216 | If you set instances number of some process to zero, this process won't be run: 217 | 218 | ```bash 219 | $ overmind start -m some_production_task=0 220 | $ OVERMIND_FORMATION=some_production_task=0 overmind start 221 | ``` 222 | 223 | #### Processes that can die 224 | 225 | Usually, when a process dies, Overmind will interrupt all other processes. However, you can specify processes that can die without interrupting all other ones: 226 | 227 | ```bash 228 | $ overmind start -c assets,npm_install 229 | $ OVERMIND_CAN_DIE=assets,npm_install overmind start 230 | ``` 231 | 232 | Also, you can allow all processes to die: 233 | 234 | ```bash 235 | $ overmind start --any-can-die 236 | $ OVERMIND_ANY_CAN_DIE=1 overmind start 237 | ``` 238 | 239 | #### Auto-restarting processes 240 | 241 | If some of your processes tend to randomly crash, you can tell Overmind to restart them automatically when they die: 242 | 243 | ```bash 244 | $ overmind start -r rails,webpack 245 | $ OVERMIND_AUTO_RESTART=rails,webpack overmind start 246 | ``` 247 | 248 | The special name `all` can also be used to restart all processes automatically when they die: 249 | 250 | ```bash 251 | $ overmind start -r all 252 | $ OVERMIND_AUTO_RESTART=all overmind start 253 | ``` 254 | 255 | > [!NOTE] 256 | > `OVERMIND_CAN_DIE` supersedes `OVERMIND_AUTO_RESTART`; if you want a restarting process, only put it in `OVERMIND_AUTO_RESTART` 257 | 258 | #### Specifying the colors 259 | 260 | Overmind colorizes process names with different colors. It may happen that these colors don't match well with your color scheme. In that case, you can specify your own colors using xterm color codes: 261 | 262 | ```bash 263 | $ overmind start -b 123,123,125,126,127 264 | $ OVERMIND_COLORS=123,123,125,126,127 overmind start 265 | ``` 266 | 267 | If you want Overmind to always use these colors, you can specify them in the [environment file](https://github.com/DarthSim/overmind#overmind-environment) located in your home directory. 268 | 269 | ### Show timestamps 270 | 271 | By default, Overmind doesn't show timestamps in its output since it expects your processes to add timestamps to their own output. But you can make Overmind to add timestamps to its output: 272 | 273 | ```bash 274 | $ overmind start -T 275 | $ OVERMIND_SHOW_TIMESTAMPS=1 overmind start 276 | ``` 277 | 278 | ### Connecting to a process 279 | 280 | If you need to gain access to process input, you can connect to its `tmux` window: 281 | 282 | ```bash 283 | $ overmind connect 284 | ``` 285 | 286 | You can safely disconnect from the window by hitting `Ctrl b` (or your tmux prefix) and then `d`. 287 | 288 | You can omit the process name to connect to the first process defined in the Procfile. 289 | 290 | If you were already in a `tmux` session when you ran `overmind connect` then you will be in a nested `tmux` session. 291 | To disconnect only from the `overmind` session, use `Ctrl b Ctrl b, d`. Sending `Ctrl b` twice will cause the command 292 | to be sent to the nested session. 293 | 294 | ### Restarting a process 295 | 296 | You can restart a single process without restarting all the other ones: 297 | 298 | ```bash 299 | $ overmind restart sidekiq 300 | ``` 301 | 302 | You can restart multiple processes the same way: 303 | 304 | ```bash 305 | $ overmind restart sidekiq assets 306 | ``` 307 | 308 | It's also possible to use wildcarded process names: 309 | 310 | ```bash 311 | $ overmind restart 'sidekiq*' 312 | ``` 313 | 314 | When the command is called without any arguments, it will restart all the processes. 315 | 316 | ### Stopping a process 317 | 318 | You can stop a single process without stopping all the other ones: 319 | 320 | ```bash 321 | $ overmind stop sidekiq 322 | ``` 323 | 324 | You can stop multiple processes the same way: 325 | 326 | ```bash 327 | $ overmind stop sidekiq assets 328 | ``` 329 | 330 | It's also possible to use wildcarded process names: 331 | 332 | ```bash 333 | $ overmind stop 'sidekiq*' 334 | ``` 335 | 336 | When the command is called without any arguments, it will stop all the processes without stopping Overmind itself. 337 | 338 | ### Killing processes 339 | 340 | If something goes wrong, you can kill all running processes: 341 | 342 | ```bash 343 | $ overmind kill 344 | ``` 345 | 346 | ### Overmind environment 347 | 348 | If you need to set specific environment variables before running a `Procfile`, you can specify them in the `.overmind.env` file in the current working directory, your home directory, or/and in the `.env` file in in the current working directory. The file should contain `variable=value` pairs, one per line: 349 | 350 | ``` 351 | PATH=$PATH:/additional/path 352 | OVERMIND_CAN_DIE=npm_install 353 | OVERMIND_PORT=3000 354 | ``` 355 | 356 | For example, if you want to use a separate `Procfile.dev` by default on a local environment, create `.overmind.env` file with `OVERMIND_PROCFILE=Procfile.dev`. Now, Overmind uses `Procfile.dev` by default. 357 | 358 | You can specify additional env files to load with `OVERMIND_ENV` variable: 359 | 360 | ```bash 361 | $ OVERMIND_ENV=./.env.local,./.env.development overmind s 362 | ``` 363 | 364 | The files will be loaded in the following order: 365 | 366 | * `~/.overmind.env` 367 | * `./.overmind.env` 368 | * `./.env` 369 | * `$OVERMIND_ENV` 370 | 371 | You can also opt to skip loading the `.env` file entirely (`.overmind.env` will still be read) by setting the variable `OVERMIND_SKIP_ENV`. 372 | 373 | #### Running a command in the Overmind environment 374 | 375 | Since you set up an environment with `.env` files, you may want to run a command inside this environment. You can do this using `run` command: 376 | 377 | ```bash 378 | $ overmind run yarn install 379 | ``` 380 | 381 | ### Run as a daemon 382 | 383 | Overmind can be run as a daemon: 384 | 385 | ```bash 386 | $ overmind start -D 387 | $ OVERMIND_DAEMONIZE=1 overmind start 388 | ``` 389 | 390 | Use the `echo` command for the logs: 391 | 392 | ```bash 393 | $ overmind echo 394 | ``` 395 | 396 | You can quit daemonized Overmind with `quit`: 397 | 398 | ```bash 399 | $ overmind quit 400 | ``` 401 | 402 | ### Specifying a socket 403 | 404 | Overmind receives commands via a Unix socket. Usually, it opens a socket named `.overmind.sock` in a working directory, but you can specify the full path: 405 | 406 | ```bash 407 | $ overmind start -s path/to/socket 408 | $ OVERMIND_SOCKET=path/to/socket overmind start 409 | ``` 410 | 411 | All other commands support the same flag: 412 | 413 | ```bash 414 | $ overmind connect -s path/to/socket web 415 | $ overmind restart -s path/to/socket sidekiq 416 | $ overmind kill -s path/to/socket 417 | ``` 418 | 419 | #### Using TCP network 420 | 421 | Overmind can bind its command center to a TCP address instead of Unix socket. This is useful when you run it on a remote machine. 422 | 423 | ```bash 424 | $ overmind start -s "0.0.0.0:4321" -S "tcp" 425 | $ OVERMIND_SOCKET="0.0.0.0:4321" OVERMIND_NETWORK="tcp" overmind start 426 | ``` 427 | 428 | You need to pass the same flags to other commands: 429 | 430 | ```bash 431 | $ overmind connect -s "0.0.0.0:4321" -S "tcp" web 432 | ``` 433 | 434 | ### Specifying tmux config 435 | 436 | Overmind can use a specified tmux config. This is useful if you want to differentiate from your main tmux window, for example adding a custom status line for Overmind or a different prefix key. 437 | 438 | ```bash 439 | overmind start -F ~/overmind.tmux.conf 440 | OVERMIND_TMUX_CONFIG=~/.overmind.tmux.conf overmind start 441 | ``` 442 | 443 | ## Known issues 444 | 445 | ### Overmind uses the system Ruby/Node/etc instead of a custom-defined one 446 | 447 | This may happen if your Ruby/Node/etc version manager isn't configured properly. Make sure that the path to your custom binaries is included in your `PATH` before the system binaries path. 448 | 449 | ### Overmind does not stop the Docker process properly 450 | 451 | Unfortunately, this is how Docker works. When you send `SIGINT` to a `docker run ...` process, it just detaches container and exits. You can solve this by using named containers and signal traps: 452 | 453 | ```procfile 454 | mydocker: trap 'docker stop mydocker' EXIT > /dev/null; docker run --name mydocker ... 455 | ``` 456 | 457 | ### Overmind can't start because of a `bind: invalid argument` error 458 | 459 | All operating systems have limits on Unix socket path length. Try to use a shorter socket path. 460 | 461 | ### Overmind exits after `pg_ctl --wait start` and keeps PostgreSQL server running 462 | 463 | Since version 12.0 `pg_ctl --wait start` exits right after starting the server. Just use the `postgres` command directly. 464 | 465 | ## Author 466 | 467 | Sergey "DarthSim" Aleksandrovich 468 | 469 | Highly inspired by [Foreman](https://github.com/ddollar/foreman). 470 | 471 | Many thanks to @antiflasher for the awesome logo. 472 | 473 | ## License 474 | 475 | Overmind is licensed under the MIT license. 476 | 477 | See LICENSE for the full license text. 478 | -------------------------------------------------------------------------------- /build-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | version=$(/bin/sh -c 'git describe --always --tags --abbrev=0') 3 | rm -rf ./dist 4 | 5 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "-s -w" -a -o dist/overmind . 6 | gzip -9 -S "-$version-linux-arm.gz" dist/overmind 7 | 8 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -a -o dist/overmind . 9 | gzip -9 -S "-$version-linux-arm64.gz" dist/overmind 10 | 11 | env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w" -a -o dist/overmind . 12 | gzip -9 -S "-$version-linux-386.gz" dist/overmind 13 | 14 | env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -a -o dist/overmind . 15 | gzip -9 -S "-$version-linux-amd64.gz" dist/overmind 16 | 17 | env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -a -o dist/overmind . 18 | gzip -9 -S "-$version-macos-amd64.gz" dist/overmind 19 | 20 | env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -a -o dist/overmind . 21 | gzip -9 -S "-$version-macos-arm64.gz" dist/overmind 22 | 23 | env CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w" -a -o dist/overmind . 24 | gzip -9 -S "-$version-freebsd-arm.gz" dist/overmind 25 | 26 | env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w" -a -o dist/overmind . 27 | gzip -9 -S "-$version-freebsd-386.gz" dist/overmind 28 | 29 | env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w" -a -o dist/overmind . 30 | gzip -9 -S "-$version-freebsd-amd64.gz" dist/overmind 31 | 32 | for filename in dist/*.gz; do 33 | sha256sum -bz $filename | awk '{printf "%s",$1}' > "$filename.sha256sum" 34 | done 35 | -------------------------------------------------------------------------------- /cmd_connect.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | 10 | "github.com/DarthSim/overmind/v2/utils" 11 | 12 | "github.com/urfave/cli" 13 | ) 14 | 15 | type cmdConnectHandler struct { 16 | dialer 17 | 18 | ControlMode bool 19 | } 20 | 21 | func (h *cmdConnectHandler) Run(c *cli.Context) error { 22 | if c.NArg() > 1 { 23 | utils.Fatal("Specify a single name of process") 24 | } 25 | 26 | conn, err := h.Dial() 27 | utils.FatalOnErr(err) 28 | 29 | fmt.Fprintf(conn, "get-connection %s\n", c.Args().First()) 30 | 31 | response, err := bufio.NewReader(conn).ReadString('\n') 32 | utils.FatalOnErr(err) 33 | 34 | response = strings.TrimSpace(response) 35 | if response == "" { 36 | utils.Fatal(fmt.Sprintf("Unknown process name: %s", c.Args().First())) 37 | } 38 | 39 | parts := strings.Split(response, " ") 40 | if len(parts) < 2 { 41 | utils.Fatal("Invalid server response") 42 | } 43 | 44 | args := []string{"-L", parts[0], "attach", "-t", parts[1]} 45 | 46 | if h.ControlMode { 47 | args = append([]string{"-CC"}, args...) 48 | } 49 | 50 | cmd := exec.Command("tmux", args...) 51 | cmd.Stdout = os.Stdout 52 | cmd.Stderr = os.Stderr 53 | cmd.Stdin = os.Stdin 54 | 55 | utils.FatalOnErr(cmd.Run()) 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd_echo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/DarthSim/overmind/v2/utils" 10 | 11 | "github.com/urfave/cli" 12 | ) 13 | 14 | type cmdEchoHandler struct{ dialer } 15 | 16 | func (h *cmdEchoHandler) Run(c *cli.Context) error { 17 | if c.Args().Present() { 18 | utils.Fatal("Echo doesn't accept any arguments") 19 | } 20 | 21 | conn, err := h.Dial() 22 | utils.FatalOnErr(err) 23 | 24 | stop := make(chan os.Signal, 1) 25 | 26 | go func() { 27 | utils.ScanLines(conn, func(b []byte) bool { 28 | fmt.Fprintf(os.Stdout, "%s\n", b) 29 | return true 30 | }) 31 | 32 | stop <- syscall.SIGINT 33 | }() 34 | 35 | fmt.Fprintln(conn, "echo") 36 | 37 | signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) 38 | 39 | <-stop 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd_kill.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DarthSim/overmind/v2/utils" 7 | 8 | "github.com/urfave/cli" 9 | ) 10 | 11 | type cmdKillHandler struct{ dialer } 12 | 13 | func (h *cmdKillHandler) Run(_ *cli.Context) error { 14 | conn, err := h.Dial() 15 | utils.FatalOnErr(err) 16 | 17 | fmt.Fprintf(conn, "kill") 18 | 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /cmd_quit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DarthSim/overmind/v2/utils" 7 | 8 | "github.com/urfave/cli" 9 | ) 10 | 11 | type cmdQuitHandler struct{ dialer } 12 | 13 | func (h *cmdQuitHandler) Run(_ *cli.Context) error { 14 | conn, err := h.Dial() 15 | utils.FatalOnErr(err) 16 | 17 | fmt.Fprintf(conn, "quit") 18 | 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /cmd_restart.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/DarthSim/overmind/v2/utils" 8 | 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | type cmdRestartHandler struct{ dialer } 13 | 14 | func (h *cmdRestartHandler) Run(c *cli.Context) error { 15 | conn, err := h.Dial() 16 | utils.FatalOnErr(err) 17 | 18 | fmt.Fprintf(conn, "restart %v\n", strings.Join(c.Args(), " ")) 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /cmd_run.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "syscall" 7 | 8 | "github.com/DarthSim/overmind/v2/utils" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | type cmdRunHandler struct{} 13 | 14 | func (h *cmdRunHandler) Run(c *cli.Context) error { 15 | if !c.Args().Present() { 16 | utils.Fatal("Specify a command to run") 17 | } 18 | 19 | command := c.Args()[0] 20 | 21 | var args []string 22 | 23 | if c.NArg() > 1 { 24 | args = c.Args()[1:] 25 | } else { 26 | args = []string{} 27 | } 28 | 29 | cmd := exec.Command(command, args...) 30 | cmd.Stdout = os.Stdout 31 | cmd.Stderr = os.Stderr 32 | cmd.Stdin = os.Stdin 33 | 34 | err := cmd.Run() 35 | 36 | if err != nil { 37 | if exitErr, ok := err.(*exec.ExitError); ok { 38 | if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 39 | os.Exit(status.ExitStatus()) 40 | } else { 41 | os.Exit(1) 42 | } 43 | } else { 44 | utils.Fatal(err) 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /cmd_status.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/DarthSim/overmind/v2/utils" 8 | 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | type cmdStatusHandler struct{ dialer } 13 | 14 | func (h *cmdStatusHandler) Run(c *cli.Context) error { 15 | if c.Args().Present() { 16 | utils.Fatal("Status doesn't accept any arguments") 17 | } 18 | 19 | conn, err := h.Dial() 20 | utils.FatalOnErr(err) 21 | 22 | fmt.Fprintln(conn, "status") 23 | 24 | utils.ScanLines(conn, func(b []byte) bool { 25 | fmt.Fprintf(os.Stdout, "%s\n", b) 26 | return true 27 | }) 28 | 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /cmd_stop.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/DarthSim/overmind/v2/utils" 8 | 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | type cmdStopHandler struct{ dialer } 13 | 14 | func (h *cmdStopHandler) Run(c *cli.Context) error { 15 | conn, err := h.Dial() 16 | utils.FatalOnErr(err) 17 | 18 | fmt.Fprintf(conn, "stop %v\n", strings.Join(c.Args(), " ")) 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /dialer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net" 4 | 5 | type dialer struct { 6 | SocketPath string 7 | Network string 8 | } 9 | 10 | func (d *dialer) Dial() (net.Conn, error) { 11 | return net.Dial(d.Network, d.SocketPath) 12 | } 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DarthSim/overmind/v2 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/matoous/go-nanoid v1.5.0 7 | github.com/sevlyar/go-daemon v0.1.6 8 | github.com/urfave/cli v1.22.12 9 | golang.org/x/term v0.4.0 10 | ) 11 | 12 | require ( 13 | github.com/Envek/godotenv v0.0.0-20240326021258-e36c8a003587 // indirect 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 15 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect 16 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 17 | golang.org/x/sys v0.4.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 2 | github.com/Envek/godotenv v0.0.0-20240326021258-e36c8a003587 h1:8oTcABPw+30WG+jMYb/uNKgIuIlk2W+GSv9kX+mdkOU= 3 | github.com/Envek/godotenv v0.0.0-20240326021258-e36c8a003587/go.mod h1:byf6gDXuYSvbwm1fZ4B1x40aOLxgcMx1AdF+/UPly/0= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= 10 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 11 | github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek= 12 | github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 16 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 17 | github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= 18 | github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= 19 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 20 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 21 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 22 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 23 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 24 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 25 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 26 | github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= 27 | github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= 28 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 29 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= 31 | golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 34 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 36 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 37 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | commands: 3 | lint: 4 | glob: "*.go" 5 | run: golangci-lint run 6 | -------------------------------------------------------------------------------- /logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /logo-sign-only.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "strings" 8 | 9 | "github.com/DarthSim/overmind/v2/start" 10 | "github.com/Envek/godotenv" 11 | 12 | "github.com/urfave/cli" 13 | ) 14 | 15 | const version = "2.5.1" 16 | 17 | func socketFlags(s, n *string) []cli.Flag { 18 | return []cli.Flag{ 19 | cli.StringFlag{ 20 | Name: "socket, s", 21 | EnvVar: "OVERMIND_SOCKET", 22 | Usage: "Path to overmind socket (in case of using unix socket) or address to bind (in other cases)", 23 | Value: "./.overmind.sock", 24 | Destination: s, 25 | }, 26 | cli.StringFlag{ 27 | Name: "network, S", 28 | EnvVar: "OVERMIND_NETWORK", 29 | Usage: "Network to use for commands. Can be 'tcp', 'tcp4', 'tcp6' or 'unix'", 30 | Value: "unix", 31 | Destination: n, 32 | }, 33 | } 34 | } 35 | 36 | func setupStartCmd() cli.Command { 37 | c := start.Handler{} 38 | 39 | return cli.Command{ 40 | Name: "start", 41 | Aliases: []string{"s"}, 42 | Usage: "Run procfile", 43 | Action: c.Run, 44 | Flags: append( 45 | []cli.Flag{ 46 | cli.StringFlag{Name: "title, w", EnvVar: "OVERMIND_TITLE", Usage: "Specify a title of the application", Destination: &c.Title}, 47 | cli.StringFlag{Name: "procfile, f", EnvVar: "OVERMIND_PROCFILE", Usage: "Specify a Procfile to load", Value: "./Procfile", Destination: &c.Procfile}, 48 | cli.StringFlag{Name: "processes, l", EnvVar: "OVERMIND_PROCESSES", Usage: "Specify process names to launch. Divide names with comma", Destination: &c.ProcNames}, 49 | cli.StringFlag{Name: "root, d", Usage: "Specify a working directory of application. Default: directory containing the Procfile", Destination: &c.Root}, 50 | cli.IntFlag{Name: "timeout, t", EnvVar: "OVERMIND_TIMEOUT", Usage: "Specify the amount of time (in seconds) processes have to shut down gracefully before being brutally killed", Value: 5, Destination: &c.Timeout}, 51 | cli.IntFlag{Name: "port, p", EnvVar: "OVERMIND_PORT,PORT", Usage: "Specify a port to use as the base", Value: 5000, Destination: &c.PortBase}, 52 | cli.IntFlag{Name: "port-step, P", EnvVar: "OVERMIND_PORT_STEP", Usage: "Specify a step to increase port number", Value: 100, Destination: &c.PortStep}, 53 | cli.BoolFlag{Name: "no-port, N", EnvVar: "OVERMIND_NO_PORT", Usage: "Don't set $PORT variable for processes", Destination: &c.NoPort}, 54 | cli.StringFlag{Name: "can-die, c", EnvVar: "OVERMIND_CAN_DIE", Usage: "Specify names of process which can die without interrupting the other processes. Divide names with comma", Destination: &c.CanDie}, 55 | cli.BoolFlag{Name: "any-can-die", EnvVar: "OVERMIND_ANY_CAN_DIE", Usage: "No dead processes should stop Overmind. Overrides can-die", Destination: &c.AnyCanDie}, 56 | cli.StringFlag{Name: "auto-restart, r", EnvVar: "OVERMIND_AUTO_RESTART", Usage: "Specify names of process which will be auto restarted on death. Divide names with comma. Use 'all' as a process name to auto restart all processes on death.", Destination: &c.AutoRestart}, 57 | cli.StringFlag{Name: "colors, b", EnvVar: "OVERMIND_COLORS", Usage: "Specify the xterm color codes that will be used to colorize process names. Divide codes with comma"}, 58 | cli.BoolFlag{Name: "show-timestamps, T", EnvVar: "OVERMIND_SHOW_TIMESTAMPS", Usage: "Add timestamps to the output", Destination: &c.ShowTimestamps}, 59 | cli.StringFlag{Name: "formation, m", EnvVar: "OVERMIND_FORMATION", Usage: "Specify the number of each process type to run. The value passed in should be in the format process=num,process=num. Use 'all' as a process name to set value for all processes"}, 60 | cli.IntFlag{Name: "formation-port-step", EnvVar: "OVERMIND_FORMATION_PORT_STEP", Usage: "Specify a step to increase port number for the next instance of a process", Value: 10, Destination: &c.FormationPortStep}, 61 | cli.StringFlag{Name: "stop-signals, i", EnvVar: "OVERMIND_STOP_SIGNALS", Usage: "Specify a signal that will be sent to each process when Overmind will try to stop them. The value passed in should be in the format process=signal,process=signal. Supported signals are: ABRT, INT, KILL, QUIT, STOP, TERM, USR1, USR2"}, 62 | cli.BoolFlag{Name: "daemonize, D", EnvVar: "OVERMIND_DAEMONIZE", Usage: "Launch Overmind as a daemon. Use 'overmind echo' to view logs and 'overmind quit' to gracefully quit daemonized instance", Destination: &c.Daemonize}, 63 | cli.StringFlag{Name: "tmux-config, F", EnvVar: "OVERMIND_TMUX_CONFIG", Usage: "Specify an alternative tmux config path to be used by Overmind", Destination: &c.TmuxConfigPath}, 64 | cli.StringFlag{Name: "ignored-processes, x", EnvVar: "OVERMIND_IGNORED_PROCESSES", Usage: "Specify process names to prevent from launching. Useful if you want to run all but one or two processes. Divide names with comma. Takes precedence over the 'processes' flag.", Destination: &c.IgnoredProcNames}, 65 | cli.StringFlag{Name: "shell, H", EnvVar: "OVERMIND_SHELL", Usage: "Specify shell to run processes with.", Value: "sh", Destination: &c.Shell}, 66 | }, 67 | socketFlags(&c.SocketPath, &c.Network)..., 68 | ), 69 | } 70 | } 71 | 72 | func setupRestartCmd() cli.Command { 73 | c := cmdRestartHandler{} 74 | 75 | return cli.Command{ 76 | Name: "restart", 77 | Aliases: []string{"r"}, 78 | Usage: "Restart specified processes", 79 | Action: c.Run, 80 | ArgsUsage: "[process name...]", 81 | Flags: socketFlags(&c.SocketPath, &c.Network), 82 | } 83 | } 84 | 85 | func setupStopCmd() cli.Command { 86 | c := cmdStopHandler{} 87 | 88 | return cli.Command{ 89 | Name: "stop", 90 | Aliases: []string{"interrupt", "i"}, 91 | Usage: "Stop specified processes without quitting Overmind itself", 92 | Action: c.Run, 93 | ArgsUsage: "[process name...]", 94 | Flags: socketFlags(&c.SocketPath, &c.Network), 95 | } 96 | } 97 | 98 | func setupConnectCmd() cli.Command { 99 | c := cmdConnectHandler{} 100 | 101 | return cli.Command{ 102 | Name: "connect", 103 | Aliases: []string{"c"}, 104 | Usage: "Connect to the tmux session of the specified process", 105 | Action: c.Run, 106 | ArgsUsage: "[process name]", 107 | Flags: append( 108 | []cli.Flag{ 109 | cli.BoolFlag{Name: "control-mode, c", EnvVar: "OVERMIND_CONTROL_MODE", Usage: "Connect to the tmux session in control mode", Destination: &c.ControlMode}, 110 | }, 111 | socketFlags(&c.SocketPath, &c.Network)..., 112 | ), 113 | } 114 | } 115 | 116 | func setupQuitCmd() cli.Command { 117 | c := cmdQuitHandler{} 118 | 119 | return cli.Command{ 120 | Name: "quit", 121 | Aliases: []string{"q"}, 122 | Usage: "Gracefully quits Overmind. Same as sending SIGINT", 123 | Action: c.Run, 124 | Flags: socketFlags(&c.SocketPath, &c.Network), 125 | } 126 | } 127 | 128 | func setupKillCmd() cli.Command { 129 | c := cmdKillHandler{} 130 | 131 | return cli.Command{ 132 | Name: "kill", 133 | Aliases: []string{"k"}, 134 | Usage: "Kills all processes", 135 | Action: c.Run, 136 | Flags: socketFlags(&c.SocketPath, &c.Network), 137 | } 138 | } 139 | 140 | func setupRunCmd() cli.Command { 141 | c := cmdRunHandler{} 142 | 143 | return cli.Command{ 144 | Name: "run", 145 | Aliases: []string{"exec", "e"}, 146 | Usage: "Runs provided command within the Overmind environment", 147 | Action: c.Run, 148 | SkipFlagParsing: true, 149 | } 150 | } 151 | 152 | func setupEchoCmd() cli.Command { 153 | c := cmdEchoHandler{} 154 | 155 | return cli.Command{ 156 | Name: "echo", 157 | Usage: "Echoes output from master Overmind instance", 158 | Action: c.Run, 159 | Flags: socketFlags(&c.SocketPath, &c.Network), 160 | } 161 | } 162 | 163 | func setupStatusCmd() cli.Command { 164 | c := cmdStatusHandler{} 165 | 166 | return cli.Command{ 167 | Name: "status", 168 | Aliases: []string{"ps"}, 169 | Usage: "Prints process statuses", 170 | Action: c.Run, 171 | Flags: socketFlags(&c.SocketPath, &c.Network), 172 | } 173 | } 174 | 175 | func main() { 176 | loadEnvFiles() 177 | 178 | app := cli.NewApp() 179 | 180 | app.Name = "Overmind" 181 | app.HelpName = "overmind" 182 | app.Usage = "The mind to rule processes of your development environment" 183 | app.Description = strings.Join([]string{ 184 | "Overmind runs commands specified in procfile in a tmux session.", 185 | "This allows to connect to each process and manage processes on fly.", 186 | }, " ") 187 | app.Author = "Sergey \"DarthSim\" Alexandrovich" 188 | app.Email = "darthsim@gmail.com" 189 | app.Version = version 190 | 191 | app.Commands = []cli.Command{ 192 | setupStartCmd(), 193 | setupRestartCmd(), 194 | setupStopCmd(), 195 | setupConnectCmd(), 196 | setupQuitCmd(), 197 | setupKillCmd(), 198 | setupRunCmd(), 199 | setupEchoCmd(), 200 | setupStatusCmd(), 201 | } 202 | 203 | app.Run(os.Args) 204 | } 205 | 206 | func loadEnvFiles() { 207 | // First load the specifically named overmind env files 208 | userHomeDir, _ := os.UserHomeDir() 209 | loadEnvFile(path.Join(userHomeDir, ".overmind.env")) 210 | loadEnvFile("./.overmind.env") 211 | 212 | _, skipEnv := os.LookupEnv("OVERMIND_SKIP_ENV") 213 | if !skipEnv { 214 | loadEnvFile("./.env") 215 | } 216 | 217 | envs := strings.Split(os.Getenv("OVERMIND_ENV"), ",") 218 | for _, e := range envs { 219 | if len(e) > 0 { 220 | loadEnvFile(e) 221 | } 222 | } 223 | } 224 | 225 | func loadEnvFile(file string) { 226 | err := godotenv.Overload(file) 227 | if err != nil { 228 | if !os.IsNotExist(err) { 229 | fmt.Fprintln(os.Stderr, "overmind: skipping", file, "due to error:", err) 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /packaging/rubygems/.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /.idea/ 9 | 10 | libexec/* 11 | !libexec/.gitkeep 12 | 13 | tmp/* 14 | !tmp/.gitkeep 15 | -------------------------------------------------------------------------------- /packaging/rubygems/.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /packaging/rubygems/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ruby '>= 3.0.0' 4 | 5 | source 'https://rubygems.org' 6 | 7 | # Specify your gem's dependencies in overmind.gemspec 8 | gemspec 9 | 10 | eval_gemfile "gemfiles/rubocop.gemfile" 11 | 12 | gem 'faraday', '~> 2.9' 13 | gem 'faraday-follow_redirects' 14 | -------------------------------------------------------------------------------- /packaging/rubygems/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | overmind (2.5.1) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | ast (2.4.2) 10 | base64 (0.1.1) 11 | diff-lcs (1.5.1) 12 | faraday (2.9.0) 13 | faraday-net_http (>= 2.0, < 3.2) 14 | faraday-follow_redirects (0.3.0) 15 | faraday (>= 1, < 3) 16 | faraday-net_http (3.1.0) 17 | net-http 18 | json (2.6.3) 19 | language_server-protocol (3.17.0.3) 20 | lint_roller (1.1.0) 21 | net-http (0.4.1) 22 | uri 23 | parallel (1.23.0) 24 | parser (3.3.0.5) 25 | ast (~> 2.4.1) 26 | racc 27 | racc (1.7.1) 28 | rainbow (3.1.1) 29 | rake (13.1.0) 30 | regexp_parser (2.8.2) 31 | rexml (3.2.6) 32 | rspec (3.13.0) 33 | rspec-core (~> 3.13.0) 34 | rspec-expectations (~> 3.13.0) 35 | rspec-mocks (~> 3.13.0) 36 | rspec-core (3.13.0) 37 | rspec-support (~> 3.13.0) 38 | rspec-expectations (3.13.0) 39 | diff-lcs (>= 1.2.0, < 2.0) 40 | rspec-support (~> 3.13.0) 41 | rspec-mocks (3.13.0) 42 | diff-lcs (>= 1.2.0, < 2.0) 43 | rspec-support (~> 3.13.0) 44 | rspec-support (3.13.1) 45 | rubocop (1.56.4) 46 | base64 (~> 0.1.1) 47 | json (~> 2.3) 48 | language_server-protocol (>= 3.17.0) 49 | parallel (~> 1.10) 50 | parser (>= 3.2.2.3) 51 | rainbow (>= 2.2.2, < 4.0) 52 | regexp_parser (>= 1.8, < 3.0) 53 | rexml (>= 3.2.5, < 4.0) 54 | rubocop-ast (>= 1.28.1, < 2.0) 55 | ruby-progressbar (~> 1.7) 56 | unicode-display_width (>= 2.4.0, < 3.0) 57 | rubocop-ast (1.31.1) 58 | parser (>= 3.3.0.4) 59 | rubocop-capybara (2.18.0) 60 | rubocop (~> 1.41) 61 | rubocop-factory_bot (2.23.1) 62 | rubocop (~> 1.33) 63 | rubocop-md (1.2.0) 64 | rubocop (>= 1.0) 65 | rubocop-performance (1.19.1) 66 | rubocop (>= 1.7.0, < 2.0) 67 | rubocop-ast (>= 0.4.0) 68 | rubocop-rspec (2.23.2) 69 | rubocop (~> 1.33) 70 | rubocop-capybara (~> 2.17) 71 | rubocop-factory_bot (~> 2.22) 72 | ruby-progressbar (1.13.0) 73 | standard (1.31.2) 74 | language_server-protocol (~> 3.17.0.2) 75 | lint_roller (~> 1.0) 76 | rubocop (~> 1.56.4) 77 | standard-custom (~> 1.0.0) 78 | standard-performance (~> 1.2) 79 | standard-custom (1.0.2) 80 | lint_roller (~> 1.0) 81 | rubocop (~> 1.50) 82 | standard-performance (1.2.1) 83 | lint_roller (~> 1.1) 84 | rubocop-performance (~> 1.19.1) 85 | unicode-display_width (2.5.0) 86 | uri (0.13.0) 87 | 88 | PLATFORMS 89 | x86_64-linux 90 | 91 | DEPENDENCIES 92 | bundler 93 | faraday (~> 2.9) 94 | faraday-follow_redirects 95 | overmind! 96 | rake (~> 13.0) 97 | rspec (~> 3.5) 98 | rubocop-md (~> 1.0)! 99 | rubocop-rspec! 100 | standard (~> 1.0)! 101 | 102 | RUBY VERSION 103 | ruby 3.2.2p53 104 | 105 | BUNDLED WITH 106 | 2.5.6 107 | -------------------------------------------------------------------------------- /packaging/rubygems/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 prog-supdex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packaging/rubygems/NOTICE: -------------------------------------------------------------------------------- 1 | LIST OF OPEN SOURCE SOFTWARE DEPENDENCIES AND LICENCES 2 | 3 | This file includes acknowledgments for the third‐parties whose software has been 4 | used in permissible forms with the overmind software. The information below 5 | lists the third party software and the license that applies for that software. 6 | 7 | The licenses that apply to these third-party resources are provided beneath the 8 | table in text. Please be aware that when using overmind, these third-party 9 | resources are under the protection of their respective license. 10 | 11 | ------------------------------------------------------------------------------ 12 | 13 | tmux 14 | 15 | THIS IS FOR INFORMATION ONLY, CODE IS UNDER THE LICENCE AT THE TOP OF ITS FILE. 16 | 17 | The README, CHANGES, FAQ and TODO files are licensed under the ISC license. All 18 | other files have a license and copyright notice at their start, typically: 19 | 20 | Copyright (c) 21 | 22 | Permission to use, copy, modify, and distribute this software for any 23 | purpose with or without fee is hereby granted, provided that the above 24 | copyright notice and this permission notice appear in all copies. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 27 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 28 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 29 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 30 | WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 31 | IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 32 | OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 33 | 34 | -------------------------------------------------------------------------------- 35 | 36 | Libevent 37 | 38 | Libevent is available for use under the following license, commonly known 39 | as the 3-clause (or "modified") BSD license: 40 | 41 | ============================== 42 | Copyright (c) 2000-2007 Niels Provos 43 | Copyright (c) 2007-2012 Niels Provos and Nick Mathewson 44 | 45 | Redistribution and use in source and binary forms, with or without 46 | modification, are permitted provided that the following conditions 47 | are met: 48 | 1. Redistributions of source code must retain the above copyright 49 | notice, this list of conditions and the following disclaimer. 50 | 2. Redistributions in binary form must reproduce the above copyright 51 | notice, this list of conditions and the following disclaimer in the 52 | documentation and/or other materials provided with the distribution. 53 | 3. The name of the author may not be used to endorse or promote products 54 | derived from this software without specific prior written permission. 55 | 56 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 57 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 58 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 59 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 60 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 61 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 62 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 63 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 64 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 65 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 66 | ============================== 67 | 68 | Portions of Libevent are based on works by others, also made available by 69 | them under the three-clause BSD license above. The copyright notices are 70 | available in the corresponding source files; the license is as above. Here's 71 | a list: 72 | 73 | log.c: 74 | Copyright (c) 2000 Dug Song 75 | Copyright (c) 1993 The Regents of the University of California. 76 | 77 | strlcpy.c: 78 | Copyright (c) 1998 Todd C. Miller 79 | 80 | win32select.c: 81 | Copyright (c) 2003 Michael A. Davis 82 | 83 | evport.c: 84 | Copyright (c) 2007 Sun Microsystems 85 | 86 | ht-internal.h: 87 | Copyright (c) 2002 Christopher Clark 88 | 89 | minheap-internal.h: 90 | Copyright (c) 2006 Maxim Yegorushkin 91 | 92 | ============================== 93 | 94 | The arc4module is available under the following, sometimes called the 95 | "OpenBSD" license: 96 | 97 | Copyright (c) 1996, David Mazieres 98 | Copyright (c) 2008, Damien Miller 99 | 100 | Permission to use, copy, modify, and distribute this software for any 101 | purpose with or without fee is hereby granted, provided that the above 102 | copyright notice and this permission notice appear in all copies. 103 | 104 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 105 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 106 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 107 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 108 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 109 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 110 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 111 | 112 | ============================== 113 | 114 | The Windows timer code is based on code from libutp, which is 115 | distributed under this license, sometimes called the "MIT" license. 116 | 117 | 118 | Copyright (c) 2010 BitTorrent, Inc. 119 | 120 | Permission is hereby granted, free of charge, to any person obtaining a copy 121 | of this software and associated documentation files (the "Software"), to deal 122 | in the Software without restriction, including without limitation the rights 123 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 124 | copies of the Software, and to permit persons to whom the Software is 125 | furnished to do so, subject to the following conditions: 126 | 127 | The above copyright notice and this permission notice shall be included in 128 | all copies or substantial portions of the Software. 129 | 130 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 131 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 132 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 133 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 134 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 135 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 136 | THE SOFTWARE. 137 | 138 | ============================== 139 | 140 | The wepoll module is available under the following, sometimes called the 141 | "FreeBSD" license: 142 | 143 | Copyright 2012-2020, Bert Belder 144 | All rights reserved. 145 | 146 | Redistribution and use in source and binary forms, with or without 147 | modification, are permitted provided that the following conditions are 148 | met: 149 | 150 | * Redistributions of source code must retain the above copyright 151 | notice, this list of conditions and the following disclaimer. 152 | 153 | * Redistributions in binary form must reproduce the above copyright 154 | notice, this list of conditions and the following disclaimer in the 155 | documentation and/or other materials provided with the distribution. 156 | 157 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 158 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 159 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 160 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 161 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 162 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 163 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 164 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 165 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 166 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 167 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 168 | 169 | ============================== 170 | 171 | The ssl-client-mbedtls.c is available under the following license: 172 | 173 | Copyright (C) 2006-2015, ARM Limited, All Rights Reserved 174 | SPDX-License-Identifier: Apache-2.0 175 | 176 | Licensed under the Apache License, Version 2.0 (the "License"); you may 177 | not use this file except in compliance with the License. 178 | You may obtain a copy of the License at 179 | 180 | http://www.apache.org/licenses/LICENSE-2.0 181 | 182 | Unless required by applicable law or agreed to in writing, software 183 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 184 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 185 | See the License for the specific language governing permissions and 186 | limitations under the License. 187 | 188 | This file is part of mbed TLS (https://tls.mbed.org) 189 | 190 | ---------------------------------------------------------------------------------- 191 | 192 | ncurses 193 | 194 | ------------------------------------------------------------------------------- 195 | -- Copyright (c) 1998-2004,2006 Free Software Foundation, Inc. -- 196 | -- -- 197 | -- Permission is hereby granted, free of charge, to any person obtaining a -- 198 | -- copy of this software and associated documentation files (the -- 199 | -- "Software"), to deal in the Software without restriction, including -- 200 | -- without limitation the rights to use, copy, modify, merge, publish, -- 201 | -- distribute, distribute with modifications, sublicense, and/or sell copies -- 202 | -- of the Software, and to permit persons to whom the Software is furnished -- 203 | -- to do so, subject to the following conditions: -- 204 | -- -- 205 | -- The above copyright notice and this permission notice shall be included -- 206 | -- in all copies or substantial portions of the Software. -- 207 | -- -- 208 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -- 209 | -- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- 210 | -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -- 211 | -- NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -- 212 | -- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -- 213 | -- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -- 214 | -- USE OR OTHER DEALINGS IN THE SOFTWARE. -- 215 | -- -- 216 | -- Except as contained in this notice, the name(s) of the above copyright -- 217 | -- holders shall not be used in advertising or otherwise to promote the -- 218 | -- sale, use or other dealings in this Software without prior written -- 219 | -- authorization. -- 220 | ------------------------------------------------------------------------------- 221 | 222 | 223 | ----------------------------------------------------------------------------------- 224 | 225 | utf8proc 226 | 227 | utf8proc is a software package originally developed 228 | by Jan Behrens and the rest of the Public Software Group, who 229 | deserve nearly all of the credit for this library, that is now maintained by the Julia-language developers. Like the original utf8proc, 230 | whose copyright and license statements are reproduced below, all new 231 | work on the utf8proc library is licensed under the [MIT "expat" 232 | license](http://opensource.org/licenses/MIT): 233 | 234 | *Copyright © 2014-2021 by Steven G. Johnson, Jiahao Chen, Tony Kelman, Jonas Fonseca, and other contributors listed in the git history.* 235 | 236 | Permission is hereby granted, free of charge, to any person obtaining a 237 | copy of this software and associated documentation files (the "Software"), 238 | to deal in the Software without restriction, including without limitation 239 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 240 | and/or sell copies of the Software, and to permit persons to whom the 241 | Software is furnished to do so, subject to the following conditions: 242 | 243 | The above copyright notice and this permission notice shall be included in 244 | all copies or substantial portions of the Software. 245 | 246 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 247 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 248 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 249 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 250 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 251 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 252 | DEALINGS IN THE SOFTWARE. 253 | 254 | ## Original utf8proc license ## 255 | 256 | *Copyright (c) 2009, 2013 Public Software Group e. V., Berlin, Germany* 257 | 258 | Permission is hereby granted, free of charge, to any person obtaining a 259 | copy of this software and associated documentation files (the "Software"), 260 | to deal in the Software without restriction, including without limitation 261 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 262 | and/or sell copies of the Software, and to permit persons to whom the 263 | Software is furnished to do so, subject to the following conditions: 264 | 265 | The above copyright notice and this permission notice shall be included in 266 | all copies or substantial portions of the Software. 267 | 268 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 269 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 270 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 271 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 272 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 273 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 274 | DEALINGS IN THE SOFTWARE. 275 | 276 | ## Unicode data license ## 277 | 278 | This software contains data (`utf8proc_data.c`) derived from processing 279 | the Unicode data files. The following license applies to that data: 280 | 281 | **COPYRIGHT AND PERMISSION NOTICE** 282 | 283 | *Copyright (c) 1991-2007 Unicode, Inc. All rights reserved. Distributed 284 | under the Terms of Use in http://www.unicode.org/copyright.html.* 285 | 286 | Permission is hereby granted, free of charge, to any person obtaining a 287 | copy of the Unicode data files and any associated documentation (the "Data 288 | Files") or Unicode software and any associated documentation (the 289 | "Software") to deal in the Data Files or Software without restriction, 290 | including without limitation the rights to use, copy, modify, merge, 291 | publish, distribute, and/or sell copies of the Data Files or Software, and 292 | to permit persons to whom the Data Files or Software are furnished to do 293 | so, provided that (a) the above copyright notice(s) and this permission 294 | notice appear with all copies of the Data Files or Software, (b) both the 295 | above copyright notice(s) and this permission notice appear in associated 296 | documentation, and (c) there is clear notice in each modified Data File or 297 | in the Software as well as in the documentation associated with the Data 298 | File(s) or Software that the data or software has been modified. 299 | 300 | THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 301 | KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 302 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 303 | THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS 304 | INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR 305 | CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 306 | USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 307 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 308 | PERFORMANCE OF THE DATA FILES OR SOFTWARE. 309 | 310 | Except as contained in this notice, the name of a copyright holder shall 311 | not be used in advertising or otherwise to promote the sale, use or other 312 | dealings in these Data Files or Software without prior written 313 | authorization of the copyright holder. 314 | 315 | Unicode and the Unicode logo are trademarks of Unicode, Inc., and may be 316 | registered in some jurisdictions. All other trademarks and registered 317 | trademarks mentioned herein are the property of their respective owners. 318 | -------------------------------------------------------------------------------- /packaging/rubygems/README.md: -------------------------------------------------------------------------------- 1 | ## Ruby wrapper Overmind 2 | This gem wraps the [Overmind](https://github.com/DarthSim/overmind) library and includes all the dependencies needed to work with it. 3 | 4 | Overmind is a process manager for Procfile-based applications and [tmux](https://tmux.github.io/). 5 | With Overmind, you can easily run several processes from your Procfile in a single terminal. 6 | 7 | Learn more about Overmind [here](https://github.com/DarthSim/overmind). 8 | 9 | ## Installation 10 | **Note:** At the moment, Overmind supports Linux, *BSD, and macOS only. 11 | 12 | ### Requirements 13 | This gem already has all the necessary dependencies and doesn't require any libraries to be installed. 14 | But for users of the *BSD system, it requires the installation of `tmux`. 15 | 16 | - **FreeBSD:** 17 | ```bash 18 | pkg install tmux 19 | ``` 20 | 21 | **Note:**: You can find more information about the `tmux` installation [here](https://github.com/tmux/tmux) 22 | 23 | ### Installation with Ruby 24 | 25 | ```bash 26 | gem install overmind 27 | ``` 28 | 29 | ### Installation with Rails 30 | Overmind can improve your DX of working on multi-process Ruby on Rails applications. 31 | First, add it to your Gemfile: 32 | 33 | ```ruby 34 | group :development do 35 | gem "overmind" 36 | end 37 | ``` 38 | 39 | We recommend installing it as a part of your project and not globally, so the version is kept in sync for everyone on your team. 40 | 41 | Then, for simplicity, we suggest generating a bin stub: 42 | 43 | ```bash 44 | bundle binstubs overmind 45 | ``` 46 | 47 | Finally, change the contents of `bin/dev` (or add this file) as follows: 48 | 49 | ```shell 50 | #!/usr/bin/env sh 51 | 52 | bin/overmind start -f Procfile.dev 53 | ``` 54 | 55 | Now, your `bin/dev` command uses Overmind under the hood. 56 | 57 | One of the biggest benefits is that now you can connect to any process and interact with it, for example, for debugging a web process: 58 | 59 | ```bash 60 | bin/overmind connect web 61 | ``` 62 | 63 | ## Usage 64 | 65 | ### Running processes 66 | 67 | Overmind reads the list of processes you want to manage from a file named `Procfile`. It may look like this: 68 | 69 | ```Procfile 70 | web: bin/rails server 71 | worker: bundle exec sidekiq 72 | assets: gulp watch 73 | ``` 74 | 75 | To get started, you just need to run Overmind from your working directory containing a `Procfile`: 76 | 77 | ```bash 78 | # in Rails project 79 | bin/overmind start 80 | 81 | # Ruby 82 | overmind start 83 | ``` 84 | 85 | #### Specifying a Procfile 86 | 87 | If a `Procfile` isn't located in your working directory, you can specify the exact path: 88 | 89 | ```bash 90 | bin/overmind start -f path/to/your/Procfile 91 | OVERMIND_PROCFILE=path/to/your/Procfile bin/overmind start 92 | ``` 93 | 94 | ### Connecting to a process 95 | 96 | If you need to gain access to process input, you can connect to its `tmux` window: 97 | 98 | ```bash 99 | bin/overmind connect 100 | ``` 101 | 102 | You can safely disconnect from the window by hitting `Ctrl b` (or your tmux prefix) and then `d`. 103 | 104 | You can omit the process name to connect to the first process defined in the Procfile. 105 | 106 | ### Restarting a process 107 | 108 | You can restart a single process without restarting all the other ones: 109 | 110 | ```bash 111 | bin/overmind restart sidekiq 112 | ``` 113 | 114 | ### Stopping a process 115 | 116 | You can stop a single process without stopping all the other ones: 117 | 118 | ```bash 119 | bin/overmind stop sidekiq 120 | ``` 121 | 122 | More features and abilities are [here](https://github.com/DarthSim/overmind) 123 | -------------------------------------------------------------------------------- /packaging/rubygems/RELEASING.md: -------------------------------------------------------------------------------- 1 | ## Releasing 2 | 3 | ### Requirements 4 | It assumes that there are already `overmind` and `tmux` binaries in the Github [release](https://github.com/DarthSim/overmind/releases/tag/v2.4.0). 5 | 6 | ## Build gem 7 | 8 | To build gems for specific platforms, your Ruby version must be at least 3.0. 9 | This requirement exists because Ruby 3.0 introduced enhancements in RubyGems, including support for the `--platform` argument, 10 | enabling the specification of target platforms for gem files. 11 | 12 | For further details on this enhancement, refer to the [RubyGems changelog](https://github.com/rubygems/rubygems/blob/master/CHANGELOG.md#enhancements-91). 13 | 14 | First, run bundle install command 15 | 16 | ```bash 17 | bundle install 18 | ``` 19 | 20 | Generate gem files for various platforms with: 21 | 22 | ```bash 23 | rake "overmind:build:all" 24 | ``` 25 | 26 | This command builds gems for Linux, macOS, and FreeBSD. 27 | 28 | To build a gem for a specific platform: 29 | 30 | ```bash 31 | rake "overmind:build:linux[amd64]" # linux x86_64 32 | rake "overmind:build:macos[arm64]" # macos arm64 33 | ``` 34 | -------------------------------------------------------------------------------- /packaging/rubygems/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Tasks are all loaded from `rakelib/*.rake`. 5 | # You may want to use `rake -T` to see what's available. 6 | # 7 | require "bundler" 8 | -------------------------------------------------------------------------------- /packaging/rubygems/bin/overmind: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'overmind/cli' 5 | 6 | begin 7 | cli = Overmind::CLI.new 8 | cli.run(ARGV) 9 | rescue StandardError => e 10 | warn e.message 11 | exit 1 12 | end 13 | -------------------------------------------------------------------------------- /packaging/rubygems/gemfiles/rubocop.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' do 4 | gem 'rubocop-md', '~> 1.0' 5 | gem 'rubocop-rspec' 6 | gem 'standard', '~> 1.0' 7 | end 8 | -------------------------------------------------------------------------------- /packaging/rubygems/lib/overmind.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Overmind; end 4 | -------------------------------------------------------------------------------- /packaging/rubygems/lib/overmind/cli.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Overmind 4 | # Command-line interface for running golang library Overmind 5 | # It ensures that the necessary dependencies, such as tmux, are present 6 | # and runs Overmind with any provided arguments. 7 | class CLI 8 | SUPPORTED_OS_REGEX = /darwin|linux|bsd/i 9 | # Path to the library's executable files 10 | LIBRARY_PATH = File.expand_path("#{File.dirname(__FILE__)}/../../libexec") 11 | OVERMIND_PATH = "#{LIBRARY_PATH}/overmind" 12 | TMUX_FOLDER_PATH = "#{LIBRARY_PATH}/prebuilt-tmux/bin" 13 | TMUX_PATH = "#{TMUX_FOLDER_PATH}/tmux" 14 | 15 | def run(args = []) 16 | os_validate! 17 | validate_tmux_present! 18 | validate_overmind_present! 19 | 20 | # Ensures arguments are properly quoted if they contain spaces 21 | args = args.map { |x| x.include?(" ") ? "'#{x}'" : x } 22 | 23 | # Use prebuild tmux if found 24 | path_with_tmux = File.exist?(TMUX_PATH) ? "#{TMUX_FOLDER_PATH}:#{ENV["PATH"]}" : ENV["PATH"] 25 | 26 | # exec the Overmind process with modified PATH if necessary 27 | exec({"PATH" => path_with_tmux}, "#{OVERMIND_PATH} #{args.join(" ")}") 28 | end 29 | 30 | private 31 | 32 | # Checks if the current OS is supported based on a regex match 33 | def os_supported? 34 | RUBY_PLATFORM.match?(SUPPORTED_OS_REGEX) 35 | end 36 | 37 | # Checks if tmux is installed either globally or as a prebuilt binary 38 | def tmux_installed? 39 | system("which tmux") || File.exist?(TMUX_PATH) 40 | end 41 | 42 | # Checks if the Overmind executable is present 43 | def overmind_installed? 44 | File.exist?(OVERMIND_PATH) 45 | end 46 | 47 | # Validates the operating system and aborts with an error message if unsupported 48 | def os_validate! 49 | return if os_supported? 50 | 51 | abort_with_message("Error: This gem supports Linux, *BSD, and macOS only.") 52 | end 53 | 54 | # Validates the presence of tmux and aborts with an error message if not found 55 | def validate_tmux_present! 56 | return if tmux_installed? 57 | 58 | abort_with_message(<<~MSG) 59 | Error: tmux not found. Please ensure tmux is installed and available in PATH. 60 | If tmux is not installed, you can usually install it using your package manager. For example: 61 | 62 | # For Ubuntu/Debian 63 | sudo apt-get install tmux 64 | #{" "} 65 | # For macOS 66 | brew install tmux 67 | #{" "} 68 | # For FreeBSD 69 | sudo pkg install tmux 70 | 71 | Installation commands might vary based on your operating system and its version.#{" "} 72 | Please consult your system's package management documentation for the most accurate instructions. 73 | MSG 74 | end 75 | 76 | # Validates the presence of Overmind and aborts with an error message if not found 77 | def validate_overmind_present! 78 | return if overmind_installed? 79 | 80 | abort_with_message("Error: Invalid platform. Overmind wasn't built for #{RUBY_PLATFORM}") 81 | end 82 | 83 | # Aborts execution with a given error message 84 | def abort_with_message(message) 85 | warn "\e[31m#{message}\e[0m" 86 | exit 1 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /packaging/rubygems/lib/overmind/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Overmind 4 | VERSION = "2.5.1" 5 | end 6 | -------------------------------------------------------------------------------- /packaging/rubygems/libexec/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarthSim/overmind/f57a6d00fc8b2eafd1d12014283eea25c7132d6e/packaging/rubygems/libexec/.gitkeep -------------------------------------------------------------------------------- /packaging/rubygems/overmind.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/overmind/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "overmind" 7 | spec.version = Overmind::VERSION 8 | spec.authors = ["prog-supdex"] 9 | spec.email = ["symeton@gmail.com"] 10 | 11 | spec.summary = "Overmind is a process manager for Procfile-based applications and tmux." 12 | spec.description = "Overmind is a process manager for Procfile-based applications and tmux." 13 | spec.homepage = "https://github.com/DarthSim/overmind" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 2.3" 16 | 17 | spec.metadata["homepage_uri"] = spec.homepage 18 | spec.metadata["source_code_uri"] = "https://github.com/DarthSim/overmind" 19 | 20 | # Specify which files should be added to the gem when it is released. 21 | spec.files = Dir["bin/*", "lib/**/*.rb", "libexec/**/*", "overmind.gemspec", "LICENSE.txt"] 22 | spec.bindir = "bin" 23 | spec.executables = ["overmind"] 24 | spec.require_paths = %w[lib libexec] 25 | 26 | spec.add_development_dependency "bundler" 27 | spec.add_development_dependency "rake", "~> 13.0" 28 | spec.add_development_dependency "rspec", "~> 3.5" 29 | end 30 | -------------------------------------------------------------------------------- /packaging/rubygems/rakelib/build_gem_with_dependencies.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../support/downloader/overmind_downloader" 4 | require_relative "../support/downloader/tmux_downloader" 5 | 6 | GEM_PLATFORMS = { 7 | "linux-arm" => "arm-linux", 8 | "linux-arm64" => "arm64-linux", 9 | "linux-386" => "x86-linux", 10 | "linux-amd64" => "x86_64-linux", 11 | "macos-amd64" => "x86_64-darwin", 12 | "macos-arm64" => "arm64-darwin", 13 | "freebsd-386" => "x86-freebsd", 14 | "freebsd-amd64" => "x86_64-freebsd", 15 | "freebsd-arm" => "arm-freebsd" 16 | }.freeze 17 | 18 | namespace :overmind do 19 | namespace :build do 20 | desc "Download `Overmind` and `tmux` binaries, and prepare gem" 21 | task :all do 22 | GEM_PLATFORMS.each do |file_platform, gem_platform| 23 | os, arch = file_platform.split("-") 24 | 25 | Overmind::Downloader::OvermindDownloader.new(os: os, arch: arch).call 26 | Overmind::Downloader::TmuxDownloader.new(os: os, arch: arch).call 27 | 28 | system("gem build overmind.gemspec --platform #{gem_platform}") 29 | end 30 | end 31 | 32 | desc "Prepare gem for Linux with optional ARCH (e.g., rake overmind:build:linux[arm64])" 33 | task :linux, [:arch] do |_t, args| 34 | build_for_os("linux", args[:arch]) 35 | end 36 | 37 | desc "Prepare gem for macOS with optional ARCH (e.g., rake overmind:build:macos[amd64])" 38 | task :macos, [:arch] do |_t, args| 39 | build_for_os("macos", args[:arch]) 40 | end 41 | 42 | desc "Prepare gem for FreeBSD with optional ARCH (e.g., rake overmind:build:freebsd[amd64])" 43 | task :freebsd, [:arch] do |_t, args| 44 | build_for_os("freebsd", args[:arch]) 45 | end 46 | 47 | def build_for_os(os, arch = nil) 48 | platforms = GEM_PLATFORMS.select { |k, _| k.start_with?(os) } 49 | platforms = platforms.select { |k, _| k.end_with?(arch) } if arch 50 | 51 | platforms.each do |file_platform, gem_platform| 52 | os, arch = file_platform.split("-") 53 | 54 | Overmind::Downloader::OvermindDownloader.new(os: os, arch: arch).call 55 | Overmind::Downloader::TmuxDownloader.new(os: os, arch: arch).call 56 | 57 | system("gem build overmind.gemspec --platform #{gem_platform}") 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /packaging/rubygems/spec/dummy/server.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | puts "Hello, World! Server started." 4 | puts "Server is running..." 5 | -------------------------------------------------------------------------------- /packaging/rubygems/spec/integration/overmind_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | require "fileutils" 5 | 6 | LOG_FILE_PATH = "tmp/overmind.log" 7 | PROFILE_PATH = "spec/dummy/Procfile" 8 | OVERMIND_SOCK = "./.overmind.sock" 9 | 10 | RSpec.describe "Overmind", type: :feature do 11 | before(:all) do 12 | FileUtils.mkdir_p("tmp") 13 | 14 | FileUtils.touch(LOG_FILE_PATH) 15 | 16 | system("overmind start -f #{PROFILE_PATH} > #{LOG_FILE_PATH} 2>&1") 17 | end 18 | 19 | after(:all) do 20 | File.delete(OVERMIND_SOCK) if File.exist?(OVERMIND_SOCK) 21 | File.delete(LOG_FILE_PATH) if File.exist?(LOG_FILE_PATH) 22 | end 23 | 24 | it "logs expected output from the process" do 25 | log_content = File.read(LOG_FILE_PATH) 26 | expect(log_content).to include("Server is running") 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /packaging/rubygems/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file was generated by the `rspec --init` command. Conventionally, all 4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 5 | # The generated `.rspec` file contains `--require spec_helper` which will cause 6 | # this file to always be loaded, without a need to explicitly require it in any 7 | # files. 8 | # 9 | # Given that it is always loaded, you are encouraged to keep this file as 10 | # light-weight as possible. Requiring heavyweight dependencies from this file 11 | # will add to the boot time of your test suite on EVERY test run, even for an 12 | # individual file that may not need all of that loaded. Instead, consider making 13 | # a separate helper file that requires the additional dependencies and performs 14 | # the additional setup, and require it from the spec files that actually need 15 | # it. 16 | 17 | # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 18 | RSpec.configure do |config| 19 | # rspec-expectations config goes here. You can use an alternate 20 | # assertion/expectation library such as wrong or the stdlib/minitest 21 | # assertions if you prefer. 22 | config.expect_with :rspec do |expectations| 23 | # This option will default to `true` in RSpec 4. It makes the `description` 24 | # and `failure_message` of custom matchers include text for helper methods 25 | # defined using `chain`, e.g.: 26 | # be_bigger_than(2).and_smaller_than(4).description 27 | # # => "be bigger than 2 and smaller than 4" 28 | # ...rather than: 29 | # # => "be bigger than 2" 30 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 31 | end 32 | 33 | # rspec-mocks config goes here. You can use an alternate test double 34 | # library (such as bogus or mocha) by changing the `mock_with` option here. 35 | config.mock_with :rspec do |mocks| 36 | # Prevents you from mocking or stubbing a method that does not exist on 37 | # a real object. This is generally recommended, and will default to 38 | # `true` in RSpec 4. 39 | mocks.verify_partial_doubles = true 40 | end 41 | 42 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 43 | # have no way to turn it off -- the option exists only for backwards 44 | # compatibility in RSpec 3). It causes shared context metadata to be 45 | # inherited by the metadata hash of host groups and examples, rather than 46 | # triggering implicit auto-inclusion in groups with matching metadata. 47 | config.shared_context_metadata_behavior = :apply_to_host_groups 48 | 49 | # The settings below are suggested to provide a good initial experience 50 | # with RSpec, but feel free to customize to your heart's content. 51 | # # This allows you to limit a spec run to individual examples or groups 52 | # # you care about by tagging them with `:focus` metadata. When nothing 53 | # # is tagged with `:focus`, all examples get run. RSpec also provides 54 | # # aliases for `it`, `describe`, and `context` that include `:focus` 55 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 56 | # config.filter_run_when_matching :focus 57 | # 58 | # # Allows RSpec to persist some state between runs in order to support 59 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 60 | # # you configure your source control system to ignore this file. 61 | # config.example_status_persistence_file_path = "spec/examples.txt" 62 | # 63 | # # Limits the available syntax to the non-monkey patched syntax that is 64 | # # recommended. For more details, see: 65 | # # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ 66 | # config.disable_monkey_patching! 67 | # 68 | # # This setting enables warnings. It's recommended, but in some cases may 69 | # # be too noisy due to issues in dependencies. 70 | # config.warnings = true 71 | # 72 | # # Many RSpec users commonly either run the entire suite or an individual 73 | # # file, and it's useful to allow more verbose output when running an 74 | # # individual spec file. 75 | # if config.files_to_run.one? 76 | # # Use the documentation formatter for detailed output, 77 | # # unless a formatter has already been configured 78 | # # (e.g. via a command-line flag). 79 | # config.default_formatter = "doc" 80 | # end 81 | # 82 | # # Print the 10 slowest examples and example groups at the 83 | # # end of the spec run, to help surface which specs are running 84 | # # particularly slow. 85 | # config.profile_examples = 10 86 | # 87 | # # Run specs in random order to surface order dependencies. If you find an 88 | # # order dependency and want to debug it, you can fix the order by providing 89 | # # the seed, which is printed after each run. 90 | # # --seed 1234 91 | # config.order = :random 92 | # 93 | # # Seed global randomization in this process using the `--seed` CLI option. 94 | # # Setting this allows you to use `--seed` to deterministically reproduce 95 | # # test failures related to randomization by passing the same `--seed` value 96 | # # as the one that triggered the failure. 97 | # Kernel.srand config.seed 98 | 99 | config.before(:each, type: :feature) do 100 | Gem::Specification.find_by_name("overmind") 101 | rescue Gem::LoadError 102 | skip "Overmind gem is not installed, skipping integration tests" 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /packaging/rubygems/support/downloader/base_downloader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "faraday" 4 | require "faraday/follow_redirects" 5 | require "fileutils" 6 | 7 | module Overmind 8 | module Downloader 9 | # Abstract base class for downloading files. 10 | # Provides common interfaces and utilities for subclasses. 11 | class BaseDownloader 12 | attr_reader :version, :os, :arch, :file_name, :download_url, :target_path, :uri 13 | 14 | # Initializes a new downloader instance. 15 | # @param os [String] the operating system for the download. 16 | # @param arch [String] the system architecture for the download. 17 | # @param version [String] the version of the file to download. 18 | def initialize(os:, arch:, version: self.class::DEFAULT_VERSION) 19 | @version = version 20 | @os = os.downcase 21 | @arch = arch.downcase 22 | @base_url = self.class::BASE_URL 23 | @target_path = self.class::TARGET_PATH 24 | @file_format = self.class::FILE_FORMAT 25 | 26 | @file_name = format(@file_format, self.class::NAME, version, os, arch) 27 | @download_url = prepare_download_url 28 | end 29 | 30 | def call 31 | unless allowed_platform? 32 | puts "Unsupported platform: #{"#{@os}-#{@arch}"}" 33 | 34 | FileUtils.rm_rf(target_path) 35 | 36 | return false 37 | end 38 | 39 | tmp_path = download_file 40 | extract_file(tmp_path) 41 | end 42 | 43 | private 44 | 45 | def allowed_platform? 46 | platform_key = "#{@os}-#{@arch}" 47 | 48 | self.class::ALLOWED_PLATFORMS.include?(platform_key) 49 | end 50 | 51 | def download_file 52 | tmp_path = File.join("tmp", file_name) 53 | response = faraday_builder.get(download_url) 54 | 55 | File.write(tmp_path, response.body, mode: "wb") 56 | 57 | tmp_path 58 | end 59 | 60 | def extract_file(_tmp_path) 61 | raise NoMethodError 62 | end 63 | 64 | def faraday_builder(args = {}) 65 | Faraday.new(args) do |builder| 66 | builder.response :logger 67 | builder.response :follow_redirects 68 | builder.response :raise_error 69 | 70 | yield(builder) if block_given? 71 | end 72 | end 73 | 74 | def prepare_download_url 75 | format(@base_url, version, file_name) 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /packaging/rubygems/support/downloader/overmind_downloader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "base_downloader" 4 | require_relative "../../lib/overmind/version" 5 | 6 | module Overmind 7 | module Downloader 8 | # Class for downloading Overmind binaries. 9 | class OvermindDownloader < BaseDownloader 10 | NAME = "overmind" 11 | DEFAULT_VERSION = ::Overmind::VERSION 12 | BASE_URL = "https://github.com/DarthSim/overmind/releases/download/v%s/%s" 13 | TARGET_PATH = "libexec/overmind" 14 | ALLOWED_PLATFORMS = %w[ 15 | linux-arm linux-arm64 linux-386 linux-amd64 macos-amd64 macos-arm64 freebsd-386 freebsd-amd64 freebsd-arm 16 | ].freeze 17 | FILE_FORMAT = "%s-v%s-%s-%s.gz" 18 | 19 | private 20 | 21 | def extract_file(tmp_path) 22 | extracted_path = tmp_path.sub(".gz", "") 23 | 24 | system("gunzip -f #{tmp_path}") 25 | 26 | FileUtils.rm(target_path) if File.exist?(target_path) 27 | FileUtils.mkdir_p(File.dirname(target_path)) 28 | FileUtils.mv(extracted_path, target_path) 29 | FileUtils.chmod("+x", target_path) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /packaging/rubygems/support/downloader/tmux_downloader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "base_downloader" 4 | 5 | module Overmind 6 | module Downloader 7 | # Class for downloading Tmux binaries. 8 | class TmuxDownloader < BaseDownloader 9 | NAME = "tmux" 10 | DEFAULT_VERSION = "3.4" 11 | BASE_URL = "https://github.com/DarthSim/overmind/releases/download/v2.4.0/%s" 12 | TARGET_PATH = "libexec/prebuilt-tmux" 13 | ALLOWED_PLATFORMS = %w[ 14 | linux-arm linux-arm64 linux-386 linux-amd64 macos-amd64 macos-arm64 15 | ].freeze 16 | 17 | FILE_FORMAT = "%s-v%s-%s-%s.tar.gz" 18 | 19 | private 20 | 21 | # Extracts the file to the specified target path. 22 | def extract_file(tmp_path) 23 | FileUtils.rm_rf(target_path) if File.exist?("#{target_path}/bin/tmux") 24 | FileUtils.mkdir_p(target_path) 25 | 26 | system("tar -xzf #{tmp_path} -C #{target_path}") 27 | 28 | tmux_executable_path = File.join(target_path, "bin/tmux") 29 | FileUtils.chmod("+x", tmux_executable_path) if File.exist?(tmux_executable_path) 30 | end 31 | 32 | def prepare_download_url 33 | format(@base_url, file_name) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /packaging/tmux/NOTICE: -------------------------------------------------------------------------------- 1 | LIST OF OPEN SOURCE SOFTWARE DEPENDENCIES AND LICENCES 2 | 3 | This file includes acknowledgments for the third‐parties whose software has been 4 | used in permissible forms with the overmind software. The information below 5 | lists the third party software and the license that applies for that software. 6 | 7 | The licenses that apply to these third-party resources are provided beneath the 8 | table in text. Please be aware that when using overmind, these third-party 9 | resources are under the protection of their respective license. 10 | 11 | ------------------------------------------------------------------------------ 12 | 13 | tmux 14 | 15 | THIS IS FOR INFORMATION ONLY, CODE IS UNDER THE LICENCE AT THE TOP OF ITS FILE. 16 | 17 | The README, CHANGES, FAQ and TODO files are licensed under the ISC license. All 18 | other files have a license and copyright notice at their start, typically: 19 | 20 | Copyright (c) 21 | 22 | Permission to use, copy, modify, and distribute this software for any 23 | purpose with or without fee is hereby granted, provided that the above 24 | copyright notice and this permission notice appear in all copies. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 27 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 28 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 29 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 30 | WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 31 | IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 32 | OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 33 | 34 | -------------------------------------------------------------------------------- 35 | 36 | Libevent 37 | 38 | Libevent is available for use under the following license, commonly known 39 | as the 3-clause (or "modified") BSD license: 40 | 41 | ============================== 42 | Copyright (c) 2000-2007 Niels Provos 43 | Copyright (c) 2007-2012 Niels Provos and Nick Mathewson 44 | 45 | Redistribution and use in source and binary forms, with or without 46 | modification, are permitted provided that the following conditions 47 | are met: 48 | 1. Redistributions of source code must retain the above copyright 49 | notice, this list of conditions and the following disclaimer. 50 | 2. Redistributions in binary form must reproduce the above copyright 51 | notice, this list of conditions and the following disclaimer in the 52 | documentation and/or other materials provided with the distribution. 53 | 3. The name of the author may not be used to endorse or promote products 54 | derived from this software without specific prior written permission. 55 | 56 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 57 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 58 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 59 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 60 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 61 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 62 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 63 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 64 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 65 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 66 | ============================== 67 | 68 | Portions of Libevent are based on works by others, also made available by 69 | them under the three-clause BSD license above. The copyright notices are 70 | available in the corresponding source files; the license is as above. Here's 71 | a list: 72 | 73 | log.c: 74 | Copyright (c) 2000 Dug Song 75 | Copyright (c) 1993 The Regents of the University of California. 76 | 77 | strlcpy.c: 78 | Copyright (c) 1998 Todd C. Miller 79 | 80 | win32select.c: 81 | Copyright (c) 2003 Michael A. Davis 82 | 83 | evport.c: 84 | Copyright (c) 2007 Sun Microsystems 85 | 86 | ht-internal.h: 87 | Copyright (c) 2002 Christopher Clark 88 | 89 | minheap-internal.h: 90 | Copyright (c) 2006 Maxim Yegorushkin 91 | 92 | ============================== 93 | 94 | The arc4module is available under the following, sometimes called the 95 | "OpenBSD" license: 96 | 97 | Copyright (c) 1996, David Mazieres 98 | Copyright (c) 2008, Damien Miller 99 | 100 | Permission to use, copy, modify, and distribute this software for any 101 | purpose with or without fee is hereby granted, provided that the above 102 | copyright notice and this permission notice appear in all copies. 103 | 104 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 105 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 106 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 107 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 108 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 109 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 110 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 111 | 112 | ============================== 113 | 114 | The Windows timer code is based on code from libutp, which is 115 | distributed under this license, sometimes called the "MIT" license. 116 | 117 | 118 | Copyright (c) 2010 BitTorrent, Inc. 119 | 120 | Permission is hereby granted, free of charge, to any person obtaining a copy 121 | of this software and associated documentation files (the "Software"), to deal 122 | in the Software without restriction, including without limitation the rights 123 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 124 | copies of the Software, and to permit persons to whom the Software is 125 | furnished to do so, subject to the following conditions: 126 | 127 | The above copyright notice and this permission notice shall be included in 128 | all copies or substantial portions of the Software. 129 | 130 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 131 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 132 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 133 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 134 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 135 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 136 | THE SOFTWARE. 137 | 138 | ============================== 139 | 140 | The wepoll module is available under the following, sometimes called the 141 | "FreeBSD" license: 142 | 143 | Copyright 2012-2020, Bert Belder 144 | All rights reserved. 145 | 146 | Redistribution and use in source and binary forms, with or without 147 | modification, are permitted provided that the following conditions are 148 | met: 149 | 150 | * Redistributions of source code must retain the above copyright 151 | notice, this list of conditions and the following disclaimer. 152 | 153 | * Redistributions in binary form must reproduce the above copyright 154 | notice, this list of conditions and the following disclaimer in the 155 | documentation and/or other materials provided with the distribution. 156 | 157 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 158 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 159 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 160 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 161 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 162 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 163 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 164 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 165 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 166 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 167 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 168 | 169 | ============================== 170 | 171 | The ssl-client-mbedtls.c is available under the following license: 172 | 173 | Copyright (C) 2006-2015, ARM Limited, All Rights Reserved 174 | SPDX-License-Identifier: Apache-2.0 175 | 176 | Licensed under the Apache License, Version 2.0 (the "License"); you may 177 | not use this file except in compliance with the License. 178 | You may obtain a copy of the License at 179 | 180 | http://www.apache.org/licenses/LICENSE-2.0 181 | 182 | Unless required by applicable law or agreed to in writing, software 183 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 184 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 185 | See the License for the specific language governing permissions and 186 | limitations under the License. 187 | 188 | This file is part of mbed TLS (https://tls.mbed.org) 189 | 190 | ---------------------------------------------------------------------------------- 191 | 192 | ncurses 193 | 194 | ------------------------------------------------------------------------------- 195 | -- Copyright (c) 1998-2004,2006 Free Software Foundation, Inc. -- 196 | -- -- 197 | -- Permission is hereby granted, free of charge, to any person obtaining a -- 198 | -- copy of this software and associated documentation files (the -- 199 | -- "Software"), to deal in the Software without restriction, including -- 200 | -- without limitation the rights to use, copy, modify, merge, publish, -- 201 | -- distribute, distribute with modifications, sublicense, and/or sell copies -- 202 | -- of the Software, and to permit persons to whom the Software is furnished -- 203 | -- to do so, subject to the following conditions: -- 204 | -- -- 205 | -- The above copyright notice and this permission notice shall be included -- 206 | -- in all copies or substantial portions of the Software. -- 207 | -- -- 208 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -- 209 | -- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- 210 | -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -- 211 | -- NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -- 212 | -- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -- 213 | -- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -- 214 | -- USE OR OTHER DEALINGS IN THE SOFTWARE. -- 215 | -- -- 216 | -- Except as contained in this notice, the name(s) of the above copyright -- 217 | -- holders shall not be used in advertising or otherwise to promote the -- 218 | -- sale, use or other dealings in this Software without prior written -- 219 | -- authorization. -- 220 | ------------------------------------------------------------------------------- 221 | 222 | 223 | ----------------------------------------------------------------------------------- 224 | 225 | utf8proc 226 | 227 | utf8proc is a software package originally developed 228 | by Jan Behrens and the rest of the Public Software Group, who 229 | deserve nearly all of the credit for this library, that is now maintained by the Julia-language developers. Like the original utf8proc, 230 | whose copyright and license statements are reproduced below, all new 231 | work on the utf8proc library is licensed under the [MIT "expat" 232 | license](http://opensource.org/licenses/MIT): 233 | 234 | *Copyright © 2014-2021 by Steven G. Johnson, Jiahao Chen, Tony Kelman, Jonas Fonseca, and other contributors listed in the git history.* 235 | 236 | Permission is hereby granted, free of charge, to any person obtaining a 237 | copy of this software and associated documentation files (the "Software"), 238 | to deal in the Software without restriction, including without limitation 239 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 240 | and/or sell copies of the Software, and to permit persons to whom the 241 | Software is furnished to do so, subject to the following conditions: 242 | 243 | The above copyright notice and this permission notice shall be included in 244 | all copies or substantial portions of the Software. 245 | 246 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 247 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 248 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 249 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 250 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 251 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 252 | DEALINGS IN THE SOFTWARE. 253 | 254 | ## Original utf8proc license ## 255 | 256 | *Copyright (c) 2009, 2013 Public Software Group e. V., Berlin, Germany* 257 | 258 | Permission is hereby granted, free of charge, to any person obtaining a 259 | copy of this software and associated documentation files (the "Software"), 260 | to deal in the Software without restriction, including without limitation 261 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 262 | and/or sell copies of the Software, and to permit persons to whom the 263 | Software is furnished to do so, subject to the following conditions: 264 | 265 | The above copyright notice and this permission notice shall be included in 266 | all copies or substantial portions of the Software. 267 | 268 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 269 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 270 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 271 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 272 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 273 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 274 | DEALINGS IN THE SOFTWARE. 275 | 276 | ## Unicode data license ## 277 | 278 | This software contains data (`utf8proc_data.c`) derived from processing 279 | the Unicode data files. The following license applies to that data: 280 | 281 | **COPYRIGHT AND PERMISSION NOTICE** 282 | 283 | *Copyright (c) 1991-2007 Unicode, Inc. All rights reserved. Distributed 284 | under the Terms of Use in http://www.unicode.org/copyright.html.* 285 | 286 | Permission is hereby granted, free of charge, to any person obtaining a 287 | copy of the Unicode data files and any associated documentation (the "Data 288 | Files") or Unicode software and any associated documentation (the 289 | "Software") to deal in the Data Files or Software without restriction, 290 | including without limitation the rights to use, copy, modify, merge, 291 | publish, distribute, and/or sell copies of the Data Files or Software, and 292 | to permit persons to whom the Data Files or Software are furnished to do 293 | so, provided that (a) the above copyright notice(s) and this permission 294 | notice appear with all copies of the Data Files or Software, (b) both the 295 | above copyright notice(s) and this permission notice appear in associated 296 | documentation, and (c) there is clear notice in each modified Data File or 297 | in the Software as well as in the documentation associated with the Data 298 | File(s) or Software that the data or software has been modified. 299 | 300 | THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 301 | KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 302 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 303 | THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS 304 | INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR 305 | CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 306 | USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 307 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 308 | PERFORMANCE OF THE DATA FILES OR SOFTWARE. 309 | 310 | Except as contained in this notice, the name of a copyright holder shall 311 | not be used in advertising or otherwise to promote the sale, use or other 312 | dealings in these Data Files or Software without prior written 313 | authorization of the copyright holder. 314 | 315 | Unicode and the Unicode logo are trademarks of Unicode, Inc., and may be 316 | registered in some jurisdictions. All other trademarks and registered 317 | trademarks mentioned herein are the property of their respective owners. 318 | -------------------------------------------------------------------------------- /packaging/tmux/README.md: -------------------------------------------------------------------------------- 1 | ## Compile tmux with dependencies 2 | 3 | We can build tmux binaries for Linux platforms (amd64/arm64/arm/386) via `Docker buildx' using 4 | ```bash 5 | docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/386 -t tmux-multiarch:latest --output type=local,dest=./dist data 6 | ``` 7 | 8 | Run into `segfaults`? See this [GitHub issue for solutions] (https://github.com/docker/buildx/issues/314#issuecomment-1043156006). 9 | 10 | 11 | For MacOS, the `tmux` binaries can be prepared by running `scripts/generate_tmux.sh` on a MacOS machine: 12 | ```bash 13 | ./scripts/generate_tmux.sh 14 | ``` 15 | 16 | This script will also work on Linux, generating `tmux` binaries for that platform. 17 | -------------------------------------------------------------------------------- /packaging/tmux/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarthSim/overmind/f57a6d00fc8b2eafd1d12014283eea25c7132d6e/packaging/tmux/data/.gitkeep -------------------------------------------------------------------------------- /packaging/tmux/data/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest AS builder 2 | 3 | RUN apk update && apk add --no-cache \ 4 | build-base \ 5 | pkgconf \ 6 | curl \ 7 | autoconf \ 8 | automake \ 9 | libtool \ 10 | bison \ 11 | openssl-dev 12 | 13 | # THe place for libraries 14 | ENV TARGETDIR=/usr/local 15 | 16 | WORKDIR /tmp 17 | 18 | # Build libevent 19 | RUN curl -LO https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz \ 20 | && tar -zxvf libevent-2.1.12-stable.tar.gz \ 21 | && cd libevent-2.1.12-stable \ 22 | && ./configure --prefix=$TARGETDIR --enable-shared && make && make install 23 | 24 | # Build ncurses 25 | RUN curl -LO https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.4.tar.gz \ 26 | && tar zxvf ncurses-6.4.tar.gz \ 27 | && cd ncurses-6.4 \ 28 | && ./configure --prefix=$TARGETDIR --with-default-terminfo-dir=/usr/share/terminfo --with-shared --with-terminfo-dirs="/etc/terminfo:/lib/terminfo:/usr/share/terminfo" --enable-pc-files --with-pkg-config-libdir=$TARGETDIR/lib/pkgconfig \ 29 | && make && make install 30 | 31 | # Download and build tmux 32 | RUN curl -LO https://github.com/tmux/tmux/releases/download/3.4/tmux-3.4.tar.gz \ 33 | && tar zxvf tmux-3.4.tar.gz \ 34 | && cd tmux-3.4 \ 35 | && PKG_CONFIG_PATH=$TARGETDIR/lib/pkgconfig ./configure --enable-static --prefix=$TARGETDIR && make && make install 36 | 37 | FROM scratch AS export-stage 38 | COPY --from=builder /usr/local/bin/tmux /bin/tmux 39 | -------------------------------------------------------------------------------- /packaging/tmux/scripts/generate_tmux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$TMUX_INSTALL_DIR" ]; then 4 | TMUX_INSTALL_DIR="/usr/local" 5 | fi 6 | 7 | TMUX_LIB_DIR="$TMUX_INSTALL_DIR/lib" 8 | 9 | OS=$(uname) 10 | echo "Detected OS: $OS" 11 | 12 | mkdir -p "$TMUX_INSTALL_DIR/bin" 13 | mkdir -p "$TMUX_LIB_DIR" 14 | LDFLAGS="-L$TMUX_LIB_DIR" 15 | CPPFLAGS="-I$TMUX_LIB_DIR/include" 16 | LIBS="-lresolv" 17 | TMUX_FLAGS="--enable-static" 18 | 19 | if [ "$OS" == "Darwin" ]; then 20 | TMUX_FLAGS="--enable-utf8proc" 21 | fi 22 | 23 | TARGETDIR="$TMUX_INSTALL_DIR" 24 | 25 | # Build libevent 26 | curl -LO https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz \ 27 | && tar -zxvf libevent-2.1.12-stable.tar.gz \ 28 | && cd libevent-2.1.12-stable \ 29 | && LDFLAGS="$LDFLAGS" CPPFLAGS="$CPPFLAGS" ./configure --prefix=$TARGETDIR --enable-shared && make && make install \ 30 | && cd .. 31 | 32 | # Build ncurses 33 | curl -LO https://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz \ 34 | && tar zxvf ncurses-5.9.tar.gz \ 35 | && cd ncurses-5.9 \ 36 | && LDFLAGS="$LDFLAGS" CPPFLAGS="$CPPFLAGS" ./configure --prefix=$TARGETDIR --with-shared --with-termlib --enable-pc-files && make && make install \ 37 | && cd .. 38 | 39 | # utf8proc for macos 40 | curl -LO https://github.com/JuliaStrings/utf8proc/releases/download/v2.9.0/utf8proc-2.9.0.tar.gz \ 41 | && tar zxvf utf8proc-2.9.0.tar.gz \ 42 | && cd utf8proc-2.9.0 \ 43 | && LDFLAGS="$LDFLAGS" CPPFLAGS="$CPPFLAGS" make prefix=$TARGETDIR && make install prefix=$TARGETDIR \ 44 | && cd .. 45 | 46 | # Download and build tmux 47 | curl -LO https://github.com/tmux/tmux/releases/download/3.4/tmux-3.4.tar.gz \ 48 | && tar zxvf tmux-3.4.tar.gz \ 49 | && cd tmux-3.4 \ 50 | && PKG_CONFIG_PATH="$TMUX_LIB_DIR/pkgconfig" LDFLAGS="$LDFLAGS" CPPFLAGS="$CPPFLAGS" LIBS="-lresolv -lutf8proc" ./configure $TMUX_FLAGS --prefix=$TARGETDIR && make && make install \ 51 | && cd .. 52 | 53 | if [ "$OS" == "Linux" ]; then 54 | find "$TARGETDIR/lib" -name "libutf8proc*.so*" -exec cp {} "$TMUX_LIB_DIR" \; 55 | fi 56 | 57 | # Copy all libraries to TMUX_LIB_DIR and refresh dependencies` paths for MacOS using install_name_tool 58 | if [ "$OS" == "Darwin" ]; then 59 | find "$TARGETDIR/lib" -name "*.dylib" -exec cp {} "$TMUX_LIB_DIR" \; 60 | 61 | for lib in "$TMUX_LIB_DIR"/*.dylib; do 62 | libname=$(basename "$lib") 63 | install_name_tool -change "$TARGETDIR/lib/$libname" "@executable_path/../lib/$libname" "$TARGETDIR/bin/tmux" 64 | done 65 | fi 66 | 67 | mv "$TARGETDIR/bin/tmux" "$TMUX_INSTALL_DIR/bin/tmux" 68 | 69 | echo "tmux has been installed to $TMUX_INSTALL_DIR/bin/tmux with dependencies in $TMUX_LIB_DIR" 70 | -------------------------------------------------------------------------------- /start/command.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "path/filepath" 9 | "regexp" 10 | "sync" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/DarthSim/overmind/v2/utils" 15 | gonanoid "github.com/matoous/go-nanoid" 16 | "github.com/sevlyar/go-daemon" 17 | ) 18 | 19 | var defaultColors = []int{2, 3, 4, 5, 6, 42, 130, 103, 129, 108} 20 | 21 | // This is a set of characters that are not allowed in a process name. 22 | // It's expressed as a negation; all character not matching the set of allowed 23 | // characters will be replaced with an underscore. 24 | var disallowedProcNameCharacters = regexp.MustCompile(`[^a-zA-Z0-9_]`) 25 | 26 | type command struct { 27 | title string 28 | timeout int 29 | tmux *tmuxClient 30 | output *multiOutput 31 | cmdCenter *commandCenter 32 | doneTrig chan bool 33 | doneWg sync.WaitGroup 34 | stopTrig chan os.Signal 35 | infoTrig chan os.Signal 36 | processes []*process 37 | scriptDir string 38 | daemonize bool 39 | } 40 | 41 | func newCommand(h *Handler) (*command, error) { 42 | pf := parseProcfile(h.Procfile, h.PortBase, h.PortStep, h.Formation, h.FormationPortStep, h.StopSignals) 43 | 44 | c := command{ 45 | timeout: h.Timeout, 46 | doneTrig: make(chan bool, len(pf)), 47 | stopTrig: make(chan os.Signal, 1), 48 | infoTrig: make(chan os.Signal, 1), 49 | processes: make([]*process, 0, len(pf)), 50 | daemonize: h.Daemonize, 51 | } 52 | 53 | root, err := h.AbsRoot() 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if len(h.Title) > 0 { 59 | c.title = h.Title 60 | } else { 61 | c.title = filepath.Base(root) 62 | } 63 | 64 | session := utils.EscapeTitle(c.title) 65 | nanoid, err := gonanoid.Nanoid() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | instanceID := fmt.Sprintf("overmind-%s-%s", session, nanoid) 71 | 72 | c.output = newMultiOutput(pf.MaxNameLength(), h.ShowTimestamps) 73 | c.tmux = newTmuxClient(session, instanceID, root, h.TmuxConfigPath, c.output.Offset()) 74 | 75 | procNames := utils.SplitAndTrim(h.ProcNames) 76 | ignoredProcNames := utils.SplitAndTrim(h.IgnoredProcNames) 77 | 78 | colors := defaultColors 79 | if len(h.Colors) > 0 { 80 | colors = h.Colors 81 | } 82 | 83 | canDie := utils.SplitAndTrim(h.CanDie) 84 | autoRestart := utils.SplitAndTrim(h.AutoRestart) 85 | 86 | c.scriptDir = filepath.Join(os.TempDir(), instanceID) 87 | os.MkdirAll(c.scriptDir, 0700) 88 | 89 | for i, e := range pf { 90 | shouldRun := len(procNames) == 0 || utils.StringsContain(procNames, e.OrigName) 91 | isIgnored := len(ignoredProcNames) != 0 && utils.StringsContain(ignoredProcNames, e.OrigName) 92 | 93 | if shouldRun && !isIgnored { 94 | scriptFilePath := c.createScriptFile(&e, pf, h.Shell, !h.NoPort) 95 | 96 | c.processes = append(c.processes, newProcess( 97 | c.tmux, 98 | e.Name, 99 | colors[i%len(colors)], 100 | scriptFilePath, 101 | c.output, 102 | (h.AnyCanDie || utils.StringsContain(canDie, e.OrigName)), 103 | (utils.StringsContain(autoRestart, e.OrigName) || utils.StringsContain(autoRestart, "all")), 104 | e.StopSignal, 105 | )) 106 | } 107 | } 108 | 109 | if len(c.processes) == 0 { 110 | return nil, errors.New("No processes to run") 111 | } 112 | 113 | c.cmdCenter = newCommandCenter(&c, h.SocketPath, h.Network) 114 | 115 | return &c, nil 116 | } 117 | 118 | func (c *command) Run() (int, error) { 119 | defer os.RemoveAll(c.scriptDir) 120 | defer c.output.Stop() 121 | 122 | fmt.Printf("\033]0;%s | overmind\007", c.title) 123 | 124 | if !c.checkTmux() { 125 | return 1, errors.New("Can't find tmux. Did you forget to install it?") 126 | } 127 | 128 | c.output.WriteBoldLinef(nil, "Tmux socket name: %v", c.tmux.Socket) 129 | c.output.WriteBoldLinef(nil, "Tmux session ID: %v", c.tmux.Session) 130 | c.output.WriteBoldLinef(nil, "Listening at %v", c.cmdCenter.SocketPath) 131 | 132 | c.startCommandCenter() 133 | defer c.stopCommandCenter() 134 | 135 | if c.daemonize { 136 | if !daemon.WasReborn() { 137 | c.stopCommandCenter() 138 | } 139 | 140 | ctx := new(daemon.Context) 141 | child, err := ctx.Reborn() 142 | 143 | if child != nil { 144 | c.output.WriteBoldLinef(nil, "Daemonized. Use `overmind echo` to view logs and `overmind quit` to gracefully quit daemonized instance") 145 | return 0, err 146 | } 147 | 148 | defer ctx.Release() 149 | } 150 | 151 | c.runProcesses() 152 | 153 | go c.waitForExit() 154 | 155 | go c.handleInfo() 156 | 157 | c.doneWg.Wait() 158 | 159 | exitCode := c.tmux.ExitCode() 160 | 161 | time.Sleep(time.Second) 162 | 163 | c.tmux.Shutdown() 164 | 165 | return exitCode, nil 166 | } 167 | 168 | func (c *command) Quit() { 169 | c.stopTrig <- syscall.SIGINT 170 | } 171 | 172 | func (c *command) createScriptFile(e *procfileEntry, procFile procfile, shell string, setPort bool) string { 173 | scriptFile, err := os.Create(filepath.Join(c.scriptDir, e.Name)) 174 | utils.FatalOnErr(err) 175 | 176 | fmt.Fprintf(scriptFile, "#!/usr/bin/env %s\n", shell) 177 | if setPort { 178 | fmt.Fprintf(scriptFile, "export PORT=%d\n", e.Port) 179 | 180 | for _, pf := range procFile { 181 | if pf.Name != e.Name { 182 | safeProcessName := sanitizeProcName(pf.Name) 183 | fmt.Fprintf(scriptFile, "export OVERMIND_PROCESS_%s_PORT=%d\n", safeProcessName, pf.Port) 184 | } 185 | } 186 | } 187 | 188 | fmt.Fprintf(scriptFile, "export PS=%s\n", e.Name) 189 | fmt.Fprintln(scriptFile, e.Command) 190 | 191 | utils.FatalOnErr(scriptFile.Chmod(0744)) 192 | 193 | utils.FatalOnErr(scriptFile.Close()) 194 | 195 | return scriptFile.Name() 196 | } 197 | 198 | func (c *command) checkTmux() bool { 199 | return utils.RunCmd("tmux", "-V") == nil 200 | } 201 | 202 | func (c *command) startCommandCenter() { 203 | utils.FatalOnErr(c.cmdCenter.Start()) 204 | } 205 | 206 | func (c *command) stopCommandCenter() { 207 | c.cmdCenter.Stop() 208 | } 209 | 210 | func (c *command) runProcesses() { 211 | for _, p := range c.processes { 212 | c.doneWg.Add(1) 213 | 214 | go func(p *process, trig chan bool, wg *sync.WaitGroup) { 215 | defer wg.Done() 216 | defer func() { trig <- true }() 217 | 218 | p.StartObserving() 219 | }(p, c.doneTrig, &c.doneWg) 220 | } 221 | 222 | utils.FatalOnErr(c.tmux.Start()) 223 | } 224 | 225 | func (c *command) waitForExit() { 226 | signal.Notify(c.stopTrig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 227 | 228 | c.waitForDoneOrStop() 229 | 230 | for _, proc := range c.processes { 231 | proc.Stop(false) 232 | } 233 | 234 | c.waitForTimeoutOrStop() 235 | 236 | for _, proc := range c.processes { 237 | proc.Kill(false) 238 | } 239 | } 240 | 241 | func (c *command) handleInfo() { 242 | signal.Notify(c.infoTrig, SIGINFO) 243 | 244 | for range c.infoTrig { 245 | for _, proc := range c.processes { 246 | proc.Info() 247 | } 248 | } 249 | } 250 | 251 | func (c *command) waitForDoneOrStop() { 252 | select { 253 | case <-c.doneTrig: 254 | case <-c.stopTrig: 255 | } 256 | } 257 | 258 | func (c *command) waitForTimeoutOrStop() { 259 | select { 260 | case <-time.After(time.Duration(c.timeout) * time.Second): 261 | case <-c.stopTrig: 262 | } 263 | } 264 | 265 | func sanitizeProcName(name string) string { 266 | return disallowedProcNameCharacters.ReplaceAllString(name, "_") 267 | } 268 | -------------------------------------------------------------------------------- /start/command_center.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/DarthSim/overmind/v2/utils" 11 | ) 12 | 13 | const ( 14 | headerProcess = "PROCESS" 15 | headerPid = "PID" 16 | headerStatus = "STATUS" 17 | ) 18 | 19 | type commandCenter struct { 20 | cmd *command 21 | listener net.Listener 22 | stop bool 23 | 24 | SocketPath string 25 | Network string 26 | } 27 | 28 | func newCommandCenter(cmd *command, socket, network string) *commandCenter { 29 | return &commandCenter{ 30 | cmd: cmd, 31 | SocketPath: socket, 32 | Network: network, 33 | } 34 | } 35 | 36 | func (c *commandCenter) Start() (err error) { 37 | if c.listener, err = net.Listen(c.Network, c.SocketPath); err != nil { 38 | if c.Network == "unix" && strings.Contains(err.Error(), "address already in use") { 39 | err = fmt.Errorf("it looks like Overmind is already running. If it's not, remove %s and try again", c.SocketPath) 40 | } 41 | return 42 | } 43 | 44 | go func(c *commandCenter) { 45 | for { 46 | if conn, err := c.listener.Accept(); err == nil { 47 | go c.handleConnection(conn) 48 | } 49 | 50 | if c.stop { 51 | break 52 | } 53 | } 54 | }(c) 55 | 56 | return nil 57 | } 58 | 59 | func (c *commandCenter) Stop() { 60 | c.stop = true 61 | c.listener.Close() 62 | } 63 | 64 | func (c *commandCenter) handleConnection(conn net.Conn) { 65 | re := regexp.MustCompile(`\S+`) 66 | 67 | utils.ScanLines(conn, func(b []byte) bool { 68 | args := re.FindAllString(string(b), -1) 69 | 70 | if len(args) == 0 { 71 | return true 72 | } 73 | 74 | cmd := args[0] 75 | 76 | if len(args) > 1 { 77 | args = args[1:] 78 | } else { 79 | args = []string{} 80 | } 81 | 82 | switch cmd { 83 | case "restart": 84 | c.processRestart(args) 85 | case "stop": 86 | c.processStop(args) 87 | case "quit": 88 | c.processQuit() 89 | case "kill": 90 | c.processKill() 91 | case "get-connection": 92 | c.processGetConnection(args, conn) 93 | case "echo": 94 | c.processEcho(conn) 95 | case "status": 96 | c.processStatus(conn) 97 | } 98 | 99 | return true 100 | }) 101 | } 102 | 103 | func (c *commandCenter) processRestart(args []string) { 104 | for _, p := range c.cmd.processes { 105 | if len(args) == 0 { 106 | p.Restart() 107 | continue 108 | } 109 | 110 | for _, pattern := range args { 111 | if utils.WildcardMatch(pattern, p.Name) { 112 | p.Restart() 113 | break 114 | } 115 | } 116 | } 117 | } 118 | 119 | func (c *commandCenter) processStop(args []string) { 120 | for _, p := range c.cmd.processes { 121 | if len(args) == 0 { 122 | p.Stop(true) 123 | continue 124 | } 125 | 126 | for _, pattern := range args { 127 | if utils.WildcardMatch(pattern, p.Name) { 128 | p.Stop(true) 129 | break 130 | } 131 | } 132 | } 133 | } 134 | 135 | func (c *commandCenter) processKill() { 136 | for _, p := range c.cmd.processes { 137 | p.Kill(false) 138 | } 139 | } 140 | 141 | func (c *commandCenter) processQuit() { 142 | c.cmd.Quit() 143 | } 144 | 145 | func (c *commandCenter) processGetConnection(args []string, conn net.Conn) { 146 | var proc *process 147 | 148 | if len(args) > 0 { 149 | name := args[0] 150 | 151 | for _, p := range c.cmd.processes { 152 | if name == p.Name { 153 | proc = p 154 | break 155 | } 156 | } 157 | } else { 158 | proc = c.cmd.processes[0] 159 | } 160 | 161 | if proc != nil { 162 | fmt.Fprintf(conn, "%s %s\n", proc.tmux.Socket, proc.WindowID()) 163 | } else { 164 | fmt.Fprintln(conn, "") 165 | } 166 | } 167 | 168 | func (c *commandCenter) processEcho(conn net.Conn) { 169 | c.cmd.output.Echo(conn) 170 | } 171 | 172 | func (c *commandCenter) processStatus(conn net.Conn) { 173 | maxNameLen := 9 174 | for _, p := range c.cmd.processes { 175 | if l := len(p.Name); l > maxNameLen { 176 | maxNameLen = l 177 | } 178 | } 179 | 180 | fmt.Fprint(conn, headerProcess) 181 | for i := maxNameLen - len(headerProcess); i > -1; i-- { 182 | conn.Write([]byte{' '}) 183 | } 184 | 185 | fmt.Fprint(conn, headerPid) 186 | fmt.Fprint(conn, " ") 187 | fmt.Fprintln(conn, headerStatus) 188 | 189 | for _, p := range c.cmd.processes { 190 | utils.FprintRpad(conn, p.Name, maxNameLen+1) 191 | utils.FprintRpad(conn, strconv.Itoa(p.pid), 10) 192 | 193 | if p.dead || p.keepingAlive { 194 | fmt.Fprintln(conn, "dead") 195 | } else { 196 | fmt.Fprintln(conn, "running") 197 | } 198 | } 199 | conn.Close() 200 | } 201 | -------------------------------------------------------------------------------- /start/handler.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | "strings" 10 | "syscall" 11 | 12 | "github.com/DarthSim/overmind/v2/utils" 13 | 14 | "github.com/urfave/cli" 15 | ) 16 | 17 | var signalMap = map[string]syscall.Signal{ 18 | "ABRT": syscall.SIGABRT, 19 | "INT": syscall.SIGINT, 20 | "KILL": syscall.SIGKILL, 21 | "QUIT": syscall.SIGQUIT, 22 | "STOP": syscall.SIGSTOP, 23 | "TERM": syscall.SIGTERM, 24 | "USR1": syscall.SIGUSR1, 25 | "USR2": syscall.SIGUSR2, 26 | } 27 | 28 | // Handler handles args and flags for the start command 29 | type Handler struct { 30 | Title string 31 | Procfile string 32 | Root string 33 | Timeout int 34 | NoPort bool 35 | PortBase, PortStep int 36 | ProcNames string 37 | IgnoredProcNames string 38 | SocketPath string 39 | Network string 40 | CanDie string 41 | AnyCanDie bool 42 | AutoRestart string 43 | Colors []int 44 | ShowTimestamps bool 45 | Formation map[string]int 46 | FormationPortStep int 47 | StopSignals map[string]syscall.Signal 48 | Daemonize bool 49 | TmuxConfigPath string 50 | Shell string 51 | } 52 | 53 | // AbsRoot returns absolute path to the working directory 54 | func (h *Handler) AbsRoot() (string, error) { 55 | var absRoot string 56 | 57 | if len(h.Root) > 0 { 58 | absRoot = h.Root 59 | } else { 60 | absRoot = filepath.Dir(h.Procfile) 61 | } 62 | 63 | return filepath.Abs(absRoot) 64 | } 65 | 66 | // Run runs the start command 67 | func (h *Handler) Run(c *cli.Context) error { 68 | err := h.parseColors(c.String("colors")) 69 | utils.FatalOnErr(err) 70 | 71 | err = h.parseFormation(c.String("formation")) 72 | utils.FatalOnErr(err) 73 | 74 | err = h.parseStopSignals(c.String("stop-signals")) 75 | utils.FatalOnErr(err) 76 | 77 | cmd, err := newCommand(h) 78 | utils.FatalOnErr(err) 79 | 80 | exitCode, err := cmd.Run() 81 | utils.FatalOnErr(err) 82 | 83 | os.Exit(exitCode) 84 | 85 | return nil 86 | } 87 | 88 | func (h *Handler) parseColors(colorsStr string) error { 89 | if len(colorsStr) > 0 { 90 | colors := strings.Split(colorsStr, ",") 91 | 92 | h.Colors = make([]int, len(colors)) 93 | 94 | for i, s := range colors { 95 | color, err := strconv.Atoi(strings.TrimSpace(s)) 96 | if err != nil || color < 0 || color > 255 { 97 | return fmt.Errorf("Invalid xterm color code: %s", s) 98 | } 99 | h.Colors[i] = color 100 | } 101 | } 102 | 103 | return nil 104 | } 105 | 106 | func (h *Handler) parseFormation(formation string) error { 107 | if len(formation) > 0 { 108 | maxProcNum := h.PortStep / h.FormationPortStep 109 | 110 | entries := strings.Split(formation, ",") 111 | 112 | h.Formation = make(map[string]int) 113 | 114 | for _, entry := range entries { 115 | pair := strings.Split(entry, "=") 116 | 117 | if len(pair) != 2 { 118 | return errors.New("Invalid formation format") 119 | } 120 | 121 | name := strings.TrimSpace(pair[0]) 122 | if len(name) == 0 { 123 | return errors.New("Invalid formation format") 124 | } 125 | 126 | num, err := strconv.Atoi(strings.TrimSpace(pair[1])) 127 | if err != nil || num < 0 { 128 | return fmt.Errorf("Invalid number of processes: %s", pair[1]) 129 | } 130 | if num > maxProcNum { 131 | return fmt.Errorf("You can spawn only %d instances of the same process with port step of %d and formation port step of %d", maxProcNum, h.PortStep, h.FormationPortStep) 132 | } 133 | 134 | h.Formation[name] = num 135 | } 136 | } 137 | 138 | return nil 139 | } 140 | 141 | func (h *Handler) parseStopSignals(signals string) error { 142 | if len(signals) > 0 { 143 | entries := strings.Split(signals, ",") 144 | 145 | h.StopSignals = make(map[string]syscall.Signal) 146 | 147 | for _, entry := range entries { 148 | pair := strings.Split(entry, "=") 149 | 150 | if len(pair) != 2 { 151 | return errors.New("Invalid stop-signals format") 152 | } 153 | 154 | name := strings.TrimSpace(pair[0]) 155 | if len(name) == 0 { 156 | return errors.New("Invalid stop-signals format") 157 | } 158 | 159 | if signal, ok := signalMap[pair[1]]; ok { 160 | h.StopSignals[name] = signal 161 | } else { 162 | return fmt.Errorf("Invalid signal: %s", pair[1]) 163 | } 164 | } 165 | } 166 | 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /start/multi_output.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/DarthSim/overmind/v2/utils" 13 | ) 14 | 15 | const ( 16 | timestampFormat = "15:04:05" 17 | outputSeparator = " | " 18 | ) 19 | 20 | type multiOutput struct { 21 | maxNameLength int 22 | showTimestamps bool 23 | 24 | ch chan *bytes.Buffer 25 | done chan struct{} 26 | 27 | echoes map[int64]io.Writer 28 | echoInd int64 29 | echoMutex sync.Mutex 30 | 31 | bufPool sync.Pool 32 | } 33 | 34 | func newMultiOutput(maxNameLength int, showTimestamps bool) *multiOutput { 35 | o := multiOutput{ 36 | maxNameLength: utils.Max(maxNameLength, 6), 37 | showTimestamps: showTimestamps, 38 | ch: make(chan *bytes.Buffer, 128), 39 | done: make(chan struct{}), 40 | echoes: make(map[int64]io.Writer), 41 | bufPool: sync.Pool{ 42 | New: func() interface{} { return new(bytes.Buffer) }, 43 | }, 44 | } 45 | 46 | go o.listen() 47 | 48 | return &o 49 | } 50 | 51 | func (o *multiOutput) Offset() int { 52 | of := o.maxNameLength + len(outputSeparator) 53 | if o.showTimestamps { 54 | of += len(timestampFormat) + 1 55 | } 56 | return of 57 | } 58 | 59 | func (o *multiOutput) listen() { 60 | for buf := range o.ch { 61 | b := buf.Bytes() 62 | 63 | os.Stdout.Write(b) 64 | 65 | if len(o.echoes) > 0 { 66 | o.writeToEchoes(b) 67 | } 68 | 69 | o.bufPool.Put(buf) 70 | } 71 | close(o.done) 72 | } 73 | 74 | func (o *multiOutput) Stop() { 75 | close(o.ch) 76 | <-o.done 77 | } 78 | 79 | func (o *multiOutput) writeToEchoes(b []byte) { 80 | o.echoMutex.Lock() 81 | defer o.echoMutex.Unlock() 82 | 83 | for i, e := range o.echoes { 84 | if _, err := e.Write(b); err != nil { 85 | delete(o.echoes, i) 86 | o.WriteBoldLinef(nil, "Echo #%d closed", i) 87 | } 88 | } 89 | } 90 | 91 | func (o *multiOutput) Echo(w io.Writer) { 92 | o.echoMutex.Lock() 93 | defer o.echoMutex.Unlock() 94 | 95 | o.echoInd++ 96 | o.echoes[o.echoInd] = w 97 | 98 | o.WriteBoldLinef(nil, "Echo #%d opened", o.echoInd) 99 | } 100 | 101 | func (o *multiOutput) WriteLine(proc *process, p []byte) { 102 | var ( 103 | name string 104 | color int 105 | ) 106 | 107 | buf := o.bufPool.Get().(*bytes.Buffer) 108 | buf.Reset() 109 | 110 | if proc != nil { 111 | name = proc.Name 112 | color = proc.Color 113 | } else { 114 | name = "system" 115 | color = 7 116 | } 117 | 118 | if o.showTimestamps { 119 | fmt.Fprintf(buf, "\033[0;38;5;%dm", color) 120 | buf.WriteString(time.Now().Format(timestampFormat)) 121 | buf.WriteByte(' ') 122 | } 123 | fmt.Fprintf(buf, "\033[1;38;5;%dm", color) 124 | utils.FprintRpad(buf, name, o.maxNameLength) 125 | buf.WriteString("\033[0m") 126 | buf.WriteString(outputSeparator) 127 | 128 | buf.Write(p) 129 | buf.WriteByte('\n') 130 | 131 | o.ch <- buf 132 | } 133 | 134 | func (o *multiOutput) WriteBoldLine(proc *process, p []byte) { 135 | o.WriteLine(proc, []byte( 136 | fmt.Sprintf("\033[1m%s\033[0m", p), 137 | )) 138 | } 139 | 140 | func (o *multiOutput) WriteBoldLinef(proc *process, format string, i ...interface{}) { 141 | o.WriteBoldLine(proc, []byte(fmt.Sprintf(format, i...))) 142 | } 143 | 144 | func (o *multiOutput) WriteErr(proc *process, err error) { 145 | for _, str := range strings.Split(err.Error(), "\n") { 146 | o.WriteLine(proc, []byte(fmt.Sprintf("\033[0;31m%v\033[0m", str))) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /start/process.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "syscall" 7 | "time" 8 | 9 | "github.com/DarthSim/overmind/v2/utils" 10 | ) 11 | 12 | const runningCheckInterval = 100 * time.Millisecond 13 | 14 | const SIGINFO syscall.Signal = 29 15 | 16 | type process struct { 17 | output *multiOutput 18 | 19 | pid int 20 | paneID string 21 | 22 | stopSignal syscall.Signal 23 | canDie bool 24 | canDieNow bool 25 | autoRestart bool 26 | keepingAlive bool 27 | dead bool 28 | interrupted bool 29 | restart bool 30 | 31 | tmux *tmuxClient 32 | 33 | in io.Writer 34 | out io.ReadCloser 35 | 36 | Name string 37 | Color int 38 | Command string 39 | } 40 | 41 | func newProcess(tmux *tmuxClient, name string, color int, command string, output *multiOutput, canDie bool, autoRestart bool, stopSignal syscall.Signal) *process { 42 | out, in := io.Pipe() 43 | 44 | proc := &process{ 45 | output: output, 46 | tmux: tmux, 47 | 48 | stopSignal: stopSignal, 49 | canDie: canDie, 50 | canDieNow: canDie, 51 | autoRestart: autoRestart, 52 | 53 | in: in, 54 | out: out, 55 | 56 | Name: name, 57 | Color: color, 58 | Command: command, 59 | } 60 | 61 | tmux.AddProcess(proc) 62 | 63 | return proc 64 | } 65 | 66 | func (p *process) WindowID() string { 67 | return fmt.Sprintf("%s:%s", p.tmux.Session, p.Name) 68 | } 69 | 70 | func (p *process) StartObserving() { 71 | if !p.Running() { 72 | p.waitPid() 73 | 74 | p.output.WriteBoldLinef(p, "Started with pid %v...", p.pid) 75 | 76 | go p.scanOuput() 77 | go p.observe() 78 | } 79 | 80 | p.Wait() 81 | } 82 | 83 | func (p *process) Wait() { 84 | ticker := time.NewTicker(runningCheckInterval) 85 | defer ticker.Stop() 86 | 87 | for range ticker.C { 88 | if p.dead { 89 | return 90 | } 91 | } 92 | } 93 | 94 | func (p *process) Running() bool { 95 | if pid := p.pid; pid > 0 { 96 | err := syscall.Kill(pid, syscall.Signal(0)) 97 | return err == nil || err == syscall.EPERM 98 | } 99 | 100 | return false 101 | } 102 | 103 | func (p *process) Stop(keepAlive bool) { 104 | p.canDieNow = keepAlive 105 | 106 | if p.interrupted { 107 | // Ok, we have tried once, time to go brutal 108 | p.Kill(keepAlive) 109 | return 110 | } 111 | 112 | if p.Running() { 113 | p.output.WriteBoldLine(p, []byte("Interrupting...")) 114 | if err := p.groupSignal(p.stopSignal); err != nil { 115 | p.output.WriteErr(p, fmt.Errorf("Can't stop: %s", err)) 116 | } 117 | } 118 | 119 | p.interrupted = true 120 | } 121 | 122 | func (p *process) Kill(keepAlive bool) { 123 | p.canDieNow = keepAlive 124 | 125 | if p.Running() { 126 | p.output.WriteBoldLine(p, []byte("Killing...")) 127 | if err := p.groupSignal(syscall.SIGKILL); err != nil { 128 | p.output.WriteErr(p, fmt.Errorf("Can't kill: %s", err)) 129 | } 130 | } 131 | } 132 | 133 | func (p *process) Info() { 134 | p.groupSignal(SIGINFO) 135 | } 136 | 137 | func (p *process) Restart() { 138 | p.restart = true 139 | p.Stop(false) 140 | } 141 | 142 | func (p *process) waitPid() { 143 | ticker := time.NewTicker(runningCheckInterval) 144 | defer ticker.Stop() 145 | 146 | for range ticker.C { 147 | if p.pid > 0 { 148 | break 149 | } 150 | } 151 | } 152 | 153 | func (p *process) groupSignal(s syscall.Signal) error { 154 | if pid := p.pid; pid > 0 { 155 | return syscall.Kill(-pid, s) 156 | } 157 | 158 | return nil 159 | } 160 | 161 | func (p *process) scanOuput() { 162 | err := utils.ScanLines(p.out, func(b []byte) bool { 163 | p.output.WriteLine(p, b) 164 | return true 165 | }) 166 | if err != nil { 167 | p.output.WriteErr(p, fmt.Errorf("Output error: %v", err)) 168 | } 169 | } 170 | 171 | func (p *process) observe() { 172 | ticker := time.NewTicker(runningCheckInterval) 173 | defer ticker.Stop() 174 | 175 | for range ticker.C { 176 | if !p.Running() { 177 | if !p.keepingAlive { 178 | p.out.Close() 179 | p.output.WriteBoldLinef(p, "Exited with code %d", p.tmux.PaneExitCode(p.paneID)) 180 | p.keepingAlive = true 181 | } 182 | 183 | if !p.canDieNow { 184 | p.keepingAlive = false 185 | p.pid = 0 186 | 187 | if p.restart || (!p.interrupted && p.autoRestart) { 188 | p.respawn() 189 | } else { 190 | p.dead = true 191 | break 192 | } 193 | } 194 | } 195 | } 196 | } 197 | 198 | func (p *process) respawn() { 199 | p.output.WriteBoldLine(p, []byte("Restarting...")) 200 | 201 | p.restart = false 202 | p.canDieNow = p.canDie 203 | p.interrupted = false 204 | 205 | p.out, p.in = io.Pipe() 206 | go p.scanOuput() 207 | 208 | p.tmux.RespawnProcess(p) 209 | 210 | p.waitPid() 211 | p.output.WriteBoldLinef(p, "Restarted with pid %v...", p.pid) 212 | } 213 | -------------------------------------------------------------------------------- /start/procfile.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "regexp" 7 | "syscall" 8 | 9 | "github.com/DarthSim/overmind/v2/utils" 10 | ) 11 | 12 | var procfileRe = regexp.MustCompile(`^([\w-]+):\s+(.+)$`) 13 | 14 | type procfileEntry struct { 15 | Name string 16 | OrigName string 17 | Command string 18 | Port int 19 | StopSignal syscall.Signal 20 | } 21 | 22 | type procfile []procfileEntry 23 | 24 | func parseProcfile(procfile string, portBase, portStep int, formation map[string]int, formationPortStep int, stopSignals map[string]syscall.Signal) (pf procfile) { 25 | f, err := os.Open(procfile) 26 | utils.FatalOnErr(err) 27 | 28 | port := portBase 29 | names := make(map[string]bool) 30 | 31 | err = utils.ScanLines(f, func(b []byte) bool { 32 | if len(b) == 0 { 33 | return true 34 | } 35 | 36 | params := procfileRe.FindStringSubmatch(string(b)) 37 | if len(params) != 3 { 38 | return true 39 | } 40 | 41 | name, cmd := params[1], params[2] 42 | 43 | num := 1 44 | if fnum, ok := formation[name]; ok { 45 | num = fnum 46 | } else if fnum, ok := formation["all"]; ok { 47 | num = fnum 48 | } 49 | 50 | signal := syscall.SIGINT 51 | if s, ok := stopSignals[name]; ok { 52 | signal = s 53 | } 54 | 55 | for i := 0; i < num; i++ { 56 | iname := name 57 | 58 | if num > 1 { 59 | iname = fmt.Sprintf("%s#%d", name, i+1) 60 | } 61 | 62 | if names[iname] { 63 | utils.Fatal("Process names must be unique") 64 | } 65 | names[iname] = true 66 | 67 | pf = append( 68 | pf, 69 | procfileEntry{ 70 | Name: iname, 71 | OrigName: name, 72 | Command: cmd, 73 | Port: port + (i * formationPortStep), 74 | StopSignal: signal, 75 | }, 76 | ) 77 | } 78 | 79 | port += portStep 80 | 81 | return true 82 | }) 83 | 84 | utils.FatalOnErr(err) 85 | 86 | if len(pf) == 0 { 87 | utils.Fatal("No entries were found in Procfile") 88 | } 89 | 90 | return 91 | } 92 | 93 | func (p procfile) MaxNameLength() (nl int) { 94 | for _, e := range p { 95 | if l := len(e.Name); nl < l { 96 | nl = l 97 | } 98 | } 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /start/tmux.go: -------------------------------------------------------------------------------- 1 | package start 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/DarthSim/overmind/v2/utils" 17 | "golang.org/x/term" 18 | ) 19 | 20 | var tmuxUnescapeRe = regexp.MustCompile(`\\(\d{3})`) 21 | var tmuxOutputRe = regexp.MustCompile(`%(\S+) (.+)`) 22 | var tmuxProcessRe = regexp.MustCompile(`%(\d+) (.+) (\d+)`) 23 | var outputRe = regexp.MustCompile(`%(\d+) (.+)`) 24 | 25 | const tmuxPaneMsgFmt = `%%overmind-process #{pane_id} %s #{pane_pid}` 26 | 27 | type tmuxClient struct { 28 | inReader, outReader io.Reader 29 | inWriter, outWriter io.Writer 30 | 31 | processes []*process 32 | processesByPane map[string]*process 33 | 34 | cmdMutex sync.Mutex 35 | 36 | cmd *exec.Cmd 37 | 38 | configPath string 39 | 40 | shutdown bool 41 | 42 | outputOffset int 43 | 44 | Root string 45 | Socket string 46 | Session string 47 | } 48 | 49 | func newTmuxClient(session, socket, root, configPath string, outputOffset int) *tmuxClient { 50 | t := tmuxClient{ 51 | processes: make([]*process, 0), 52 | processesByPane: make(map[string]*process), 53 | 54 | configPath: configPath, 55 | 56 | outputOffset: outputOffset, 57 | 58 | Root: root, 59 | Session: session, 60 | Socket: socket, 61 | } 62 | 63 | t.inReader, t.inWriter = io.Pipe() 64 | t.outReader, t.outWriter = io.Pipe() 65 | 66 | return &t 67 | } 68 | 69 | func (t *tmuxClient) Start() error { 70 | go t.listen() 71 | 72 | args := []string{"-C", "-L", t.Socket} 73 | 74 | if len(t.configPath) != 0 { 75 | args = append(args, "-f", t.configPath) 76 | } 77 | 78 | first := true 79 | for _, p := range t.processes { 80 | tmuxPaneMsg := fmt.Sprintf(tmuxPaneMsgFmt, p.Name) 81 | 82 | if first { 83 | first = false 84 | 85 | args = append(args, "new", "-n", p.Name, "-s", t.Session, "-P", "-F", tmuxPaneMsg, p.Command, ";") 86 | 87 | if w, h, err := term.GetSize(int(os.Stdin.Fd())); err == nil { 88 | if w > t.outputOffset { 89 | w -= t.outputOffset 90 | } 91 | 92 | args = append(args, "refresh", "-C", fmt.Sprintf("%d,%d", w, h), ";") 93 | } 94 | 95 | args = append(args, "setw", "-g", "remain-on-exit", "on", ";") 96 | args = append(args, "setw", "-g", "allow-rename", "off", ";") 97 | } else { 98 | args = append(args, "neww", "-n", p.Name, "-P", "-F", tmuxPaneMsg, p.Command, ";") 99 | } 100 | } 101 | 102 | t.cmd = exec.Command("tmux", args...) 103 | t.cmd.Stdout = t.outWriter 104 | t.cmd.Stderr = os.Stderr 105 | t.cmd.Stdin = t.inReader 106 | t.cmd.Dir = t.Root 107 | 108 | err := t.cmd.Start() 109 | if err != nil { 110 | return err 111 | } 112 | 113 | go t.observe() 114 | 115 | return nil 116 | } 117 | 118 | func (t *tmuxClient) sendCmd(cmd string, arg ...interface{}) { 119 | t.cmdMutex.Lock() 120 | defer t.cmdMutex.Unlock() 121 | 122 | fmt.Fprintln(t.inWriter, fmt.Sprintf(cmd, arg...)) 123 | } 124 | 125 | func (t *tmuxClient) listen() { 126 | scanner := bufio.NewScanner(t.outReader) 127 | 128 | for scanner.Scan() { 129 | // fmt.Println(scanner.Text()) 130 | tmuxOut := tmuxOutputRe.FindStringSubmatch(scanner.Text()) 131 | 132 | if len(tmuxOut) < 2 { 133 | continue 134 | } 135 | 136 | switch tmuxOut[1] { 137 | case "overmind-process": 138 | procbind := tmuxProcessRe.FindStringSubmatch(tmuxOut[2]) 139 | if len(procbind) > 3 { 140 | t.mapProcess(procbind[1], procbind[2], procbind[3]) 141 | } 142 | case "output": 143 | output := outputRe.FindStringSubmatch(tmuxOut[2]) 144 | if len(output) > 2 { 145 | t.sendOutput(output[1], output[2]) 146 | } 147 | } 148 | } 149 | 150 | utils.FatalOnErr(scanner.Err()) 151 | } 152 | 153 | func (t *tmuxClient) observe() { 154 | t.cmd.Process.Wait() 155 | 156 | if t.shutdown { 157 | return 158 | } 159 | 160 | exec.Command("tmux", "-L", t.Socket, "kill-session", "-t", t.Session).Run() 161 | 162 | utils.Fatal("Tmux client unexpectedly exited") 163 | } 164 | 165 | func (t *tmuxClient) mapProcess(pane, name, pid string) { 166 | for _, p := range t.processes { 167 | if p.Name != name { 168 | continue 169 | } 170 | 171 | t.processesByPane[pane] = p 172 | p.paneID = pane // save the tmux paneID in the process 173 | 174 | if ipid, err := strconv.Atoi(pid); err == nil { 175 | p.pid = ipid 176 | } 177 | 178 | break 179 | } 180 | } 181 | 182 | func (t *tmuxClient) sendOutput(name, str string) { 183 | if proc, ok := t.processesByPane[name]; ok { 184 | unescaped := tmuxUnescapeRe.ReplaceAllStringFunc(str, func(src string) string { 185 | code, _ := strconv.ParseUint(src[1:], 8, 8) 186 | return string([]byte{byte(code)}) 187 | }) 188 | 189 | fmt.Fprint(proc.in, unescaped) 190 | } 191 | } 192 | 193 | func (t *tmuxClient) AddProcess(p *process) { 194 | t.processes = append(t.processes, p) 195 | } 196 | 197 | func (t *tmuxClient) RespawnProcess(p *process) { 198 | command := strings.ReplaceAll(fmt.Sprintf("%q", p.Command), "$", "\\$") 199 | tmuxPaneMsg := fmt.Sprintf(tmuxPaneMsgFmt, p.Name) 200 | t.sendCmd("neww -d -k -t %s -n %s -P -F %q %s", p.Name, p.Name, tmuxPaneMsg, command) 201 | } 202 | 203 | func (t *tmuxClient) ExitCode() (status int) { 204 | buf := new(bytes.Buffer) 205 | 206 | cmd := exec.Command("tmux", "-L", t.Socket, "list-windows", "-t", t.Session, "-F", "#{pane_dead_status}") 207 | cmd.Stdout = buf 208 | cmd.Stderr = os.Stderr 209 | cmd.Run() 210 | 211 | scanner := bufio.NewScanner(buf) 212 | 213 | for scanner.Scan() { 214 | if s, err := strconv.Atoi(scanner.Text()); err == nil && s > status { 215 | status = s 216 | } 217 | } 218 | 219 | return 220 | } 221 | 222 | func (t *tmuxClient) Shutdown() { 223 | t.shutdown = true 224 | 225 | t.sendCmd("kill-session") 226 | 227 | stopped := make(chan struct{}) 228 | 229 | go func() { 230 | t.cmd.Process.Wait() 231 | close(stopped) 232 | }() 233 | 234 | select { 235 | case <-stopped: 236 | case <-time.After(5 * time.Second): 237 | } 238 | } 239 | 240 | func (t *tmuxClient) PaneExitCode(paneID string) int { 241 | cmd := exec.Command("tmux", "-L", t.Socket, "display-message", "-p", "-t", "%"+paneID, "#{pane_dead_status}") 242 | var out bytes.Buffer 243 | cmd.Stdout = &out 244 | cmd.Stderr = os.Stderr 245 | cmd.Run() 246 | 247 | status, _ := strconv.Atoi(strings.TrimSpace(out.String())) 248 | 249 | return status 250 | } 251 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "os" 10 | "os/exec" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | var badTitleCharsRe = regexp.MustCompile(`[^a-zA-Z0-9]`) 16 | var dashesRe = regexp.MustCompile(`-{2,}`) 17 | 18 | // FatalOnErr prints error and exits if errir is not nil 19 | func FatalOnErr(err error) { 20 | if err != nil { 21 | Fatal(err) 22 | } 23 | } 24 | 25 | // Fatal prints error and exits if errir 26 | func Fatal(i ...interface{}) { 27 | fmt.Fprint(os.Stderr, "overmind: ") 28 | fmt.Fprintln(os.Stderr, i...) 29 | os.Exit(1) 30 | } 31 | 32 | // EscapeTitle makes title usable for tmux session name 33 | func EscapeTitle(title string) string { 34 | return strings.ToLower( 35 | dashesRe.ReplaceAllString(badTitleCharsRe.ReplaceAllString(title, "-"), "-"), 36 | ) 37 | } 38 | 39 | // RunCmd runs shell command and returns running error 40 | func RunCmd(cmd string, args ...string) error { 41 | return exec.Command(cmd, args...).Run() 42 | } 43 | 44 | // SplitAndTrim splits string, trims every entry and removes blank entries 45 | func SplitAndTrim(str string) (res []string) { 46 | split := strings.Split(str, ",") 47 | for _, s := range split { 48 | s = strings.Trim(s, " ") 49 | if len(s) > 0 { 50 | res = append(res, s) 51 | } 52 | } 53 | return 54 | } 55 | 56 | // StringsContain returns true if provided string slice contains provided string 57 | func StringsContain(strs []string, str string) bool { 58 | for _, s := range strs { 59 | if s == str { 60 | return true 61 | } 62 | } 63 | return false 64 | } 65 | 66 | // WildcardMatch returns true if provided string matches provided wildcard 67 | func WildcardMatch(pattern, str string) bool { 68 | re := regexp.MustCompile( 69 | fmt.Sprintf( 70 | "^%s$", 71 | strings.ReplaceAll(regexp.QuoteMeta(pattern), "\\*", ".*"), 72 | ), 73 | ) 74 | 75 | return re.MatchString(str) 76 | } 77 | 78 | // Max finds max integer 79 | func Max(a, b int) int { 80 | if b > a { 81 | return b 82 | } 83 | return a 84 | } 85 | 86 | // ScanLines reads line by line from reader. Doesn't throw "token too long" error like bufio.Scanner 87 | func ScanLines(r io.Reader, callback func([]byte) bool) error { 88 | var ( 89 | err error 90 | line []byte 91 | isPrefix bool 92 | ) 93 | 94 | reader := bufio.NewReader(r) 95 | buf := new(bytes.Buffer) 96 | 97 | for { 98 | line, isPrefix, err = reader.ReadLine() 99 | if err != nil { 100 | break 101 | } 102 | 103 | buf.Write(line) 104 | 105 | if !isPrefix { 106 | if !callback(buf.Bytes()) { 107 | return nil 108 | } 109 | buf.Reset() 110 | } 111 | } 112 | if err != io.EOF && err != io.ErrClosedPipe { 113 | return err 114 | } 115 | return nil 116 | } 117 | 118 | func FprintRpad(w io.Writer, str string, l int) { 119 | fmt.Fprint(w, str) 120 | for i := l - len(str); i > 0; i-- { 121 | w.Write([]byte{' '}) 122 | } 123 | } 124 | 125 | // ConvertError converts specific errors to the standard error type 126 | func ConvertError(err error) error { 127 | if exErr, ok := err.(*exec.ExitError); ok { 128 | return errors.New(string(exErr.Stderr)) 129 | } 130 | 131 | return err 132 | } 133 | --------------------------------------------------------------------------------