├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
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 | 
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 |
16 |
--------------------------------------------------------------------------------
/logo-sign-only.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------