procsd 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +206 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/procsd +6 -0
- data/lib/procsd.rb +6 -0
- data/lib/procsd/cli.rb +238 -0
- data/lib/procsd/generator.rb +44 -0
- data/lib/procsd/templates/service.erb +21 -0
- data/lib/procsd/templates/target.erb +6 -0
- data/lib/procsd/version.rb +3 -0
- data/procsd.gemspec +31 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bb2d76f653bcd73538afe6175c6e910bf77bbe86930238bfd7ec53f2bef8e939
|
4
|
+
data.tar.gz: 805808a883ea1f4c8ae03a01a390dda70729dea2a948e16e24c2b392791fa7cb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e8e6057f5bc9d8a3d466a7f57fb958f0335812a11412ab1b71ef4fa4ea3a77afe37b4d49725f2c144014afcd1ede0f218fc1c3165dbf10df7608c6763848543
|
7
|
+
data.tar.gz: 32559bb91e97ed3cc238849b5bbc55609cafb27bd0ae25414cdf9fcf91e6205a35b0b6cec65fd9b6339fb153bc5eb8e5a8cfdc3851296bb8df9dad7ae00e6cb1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Victor Afanasev
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# Procsd
|
2
|
+
|
3
|
+
I do like the way how simple is managing of application processes in production on Heroku with [Procfile](https://devcenter.heroku.com/articles/procfile). How easily can be accessed application logs with [heroku logs](https://devcenter.heroku.com/articles/logging) command. Just type `heroku create` and you're good to go.
|
4
|
+
|
5
|
+
Can we have something similar on the cheap Ubuntu VPS from DigitalOcean? Yes we can, all we need is a Systemd wrapper which allows to export application processes from Procfile to system services, and control them/check status/access logs using simple commands.
|
6
|
+
|
7
|
+
> These days most of Linux distributions (including Ubuntu) has Systemd as a default system processes manager. That's why it is a good idea to use Systemd for managing application processes in production (for simple cases).
|
8
|
+
|
9
|
+
## Getting started
|
10
|
+
|
11
|
+
> Install `procsd` first: `$ gem install procsd`. Required Ruby version is `>= 2.3.0`.
|
12
|
+
|
13
|
+
Let's say you have following application's Procfile:
|
14
|
+
|
15
|
+
```yaml
|
16
|
+
web: bundle exec rails server -p $PORT
|
17
|
+
worker: bundle exec sidekiq -e $RAILS_ENV
|
18
|
+
```
|
19
|
+
and you want to have one instance of web process && two instances of worker process. Create inside application directory `.procsd.yml` config file:
|
20
|
+
|
21
|
+
```yaml
|
22
|
+
app: sample_app
|
23
|
+
formation: web=1,worker=2
|
24
|
+
environment:
|
25
|
+
- PORT=2500
|
26
|
+
- RAILS_ENV=production
|
27
|
+
- RAILS_LOG_TO_STDOUT=true
|
28
|
+
```
|
29
|
+
> The only required option in `.procsd.yml` is `app` (application's name). Also you can provide custom Systemd directory path (`systemd_dir` option, default is _/etc/systemd/system_)
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
Configuration is done. Now it's time to **create** an application (export it to Systemd):
|
34
|
+
> To disable and remove application from Systemd there is command `$ procsd destroy`.
|
35
|
+
|
36
|
+
> Note: `create` command needs to provide a few arguments: _--user_ (name of the current user), _--dir_ (application's working directory) and `--path` (user's $PATH). Usually it's fine to provide them like on example below:
|
37
|
+
|
38
|
+
```
|
39
|
+
deploy@server:~/sample_app$ procsd create -u $USER -d $PWD -p $PATH
|
40
|
+
|
41
|
+
Systemd directory: /etc/systemd/system
|
42
|
+
create sample_app-web.1.service
|
43
|
+
create sample_app-worker.1.service
|
44
|
+
create sample_app-worker.2.service
|
45
|
+
create sample_app.target
|
46
|
+
Created symlink /etc/systemd/system/multi-user.target.wants/sample_app.target → /etc/systemd/system/sample_app.target.
|
47
|
+
Enabled app target sample_app.target
|
48
|
+
Reloaded configuraion (daemon-reload)
|
49
|
+
App services were created and enabled. Run `start` to start them
|
50
|
+
```
|
51
|
+
|
52
|
+
|
53
|
+
**Start** application services:
|
54
|
+
> Other control commands: `stop` and `restart`
|
55
|
+
|
56
|
+
```
|
57
|
+
deploy@server:~/sample_app$ procsd start
|
58
|
+
|
59
|
+
Started app services (sample_app.target)
|
60
|
+
```
|
61
|
+
|
62
|
+
|
63
|
+
Check the **status**:
|
64
|
+
> You can filter processes, like `$ procsd status worker` (show status only for worker processes) or `$ procsd status worker.2` (show status only for worker.2 process)
|
65
|
+
|
66
|
+
> To show status of the main application target: `$ procsd status --target`
|
67
|
+
|
68
|
+
```
|
69
|
+
deploy@server:~/sample_app$ procsd status
|
70
|
+
|
71
|
+
● sample_app-web.1.service
|
72
|
+
Loaded: loaded (/etc/systemd/system/sample_app-web.1.service; static; vendor preset: enabled)
|
73
|
+
Active: active (running) since Sun 2018-11-04 01:54:15 +04; 1min 51s ago
|
74
|
+
Main PID: 8828 (ruby)
|
75
|
+
Tasks: 13 (limit: 4915)
|
76
|
+
Memory: 83.6M
|
77
|
+
CGroup: /system.slice/sample_app-web.1.service
|
78
|
+
└─8828 puma 3.12.0 (tcp://0.0.0.0:2500) [sample_app]
|
79
|
+
|
80
|
+
2018-11-04T01:54:15+0400 systemd[1]: Started sample_app-web.1.service.
|
81
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: => Booting Puma
|
82
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: => Rails 5.2.1 application starting in production
|
83
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: => Run `rails server -h` for more startup options
|
84
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: Puma starting in single mode...
|
85
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Version 3.12.0 (ruby 2.3.0-p0), codename: Llamas in Pajamas
|
86
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Min threads: 5, max threads: 5
|
87
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Environment: production
|
88
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Listening on tcp://0.0.0.0:2500
|
89
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: Use Ctrl-C to stop
|
90
|
+
|
91
|
+
● sample_app-worker.1.service
|
92
|
+
Loaded: loaded (/etc/systemd/system/sample_app-worker.1.service; static; vendor preset: enabled)
|
93
|
+
Active: active (running) since Sun 2018-11-04 01:54:15 +04; 1min 51s ago
|
94
|
+
Main PID: 8826 (bundle)
|
95
|
+
Tasks: 15 (limit: 4915)
|
96
|
+
Memory: 87.8M
|
97
|
+
CGroup: /system.slice/sample_app-worker.1.service
|
98
|
+
└─8826 sidekiq 5.2.2 sample_app [0 of 10 busy]
|
99
|
+
|
100
|
+
2018-11-04T01:54:15+0400 systemd[1]: Started sample_app-worker.1.service.
|
101
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: Running in ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
|
102
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: See LICENSE and the LGPL-3.0 for licensing details.
|
103
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
|
104
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: Booting Sidekiq 5.2.2 with redis options {:id=>"Sidekiq-serv…, :url=>nil}
|
105
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.658Z 8826 TID-grcvqfyom INFO: Starting processing, hit Ctrl-C to stop
|
106
|
+
|
107
|
+
● sample_app-worker.2.service
|
108
|
+
Loaded: loaded (/etc/systemd/system/sample_app-worker.2.service; static; vendor preset: enabled)
|
109
|
+
Active: active (running) since Sun 2018-11-04 01:54:15 +04; 1min 51s ago
|
110
|
+
Main PID: 8827 (bundle)
|
111
|
+
Tasks: 15 (limit: 4915)
|
112
|
+
Memory: 87.8M
|
113
|
+
CGroup: /system.slice/sample_app-worker.2.service
|
114
|
+
└─8827 sidekiq 5.2.2 sample_app [0 of 10 busy]
|
115
|
+
|
116
|
+
2018-11-04T01:54:15+0400 systemd[1]: Started sample_app-worker.2.service.
|
117
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.713Z 8827 TID-gniahzm1r INFO: Running in ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
|
118
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.713Z 8827 TID-gniahzm1r INFO: See LICENSE and the LGPL-3.0 for licensing details.
|
119
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.714Z 8827 TID-gniahzm1r INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
|
120
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.714Z 8827 TID-gniahzm1r INFO: Booting Sidekiq 5.2.2 with redis options {:id=>"Sidekiq-serv…, :url=>nil}
|
121
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.716Z 8827 TID-gniahzm1r INFO: Starting processing, hit Ctrl-C to stop
|
122
|
+
```
|
123
|
+
|
124
|
+
|
125
|
+
Now the interesting part, **logs**:
|
126
|
+
> Like with command `status`, you can filter logs by passing the name of process as an argument: `$ procsd logs web` (show logs only for web processes, if any)
|
127
|
+
|
128
|
+
```
|
129
|
+
deploy@server:~/sample_app$ procsd logs
|
130
|
+
|
131
|
+
-- Logs begin at Sun 2018-10-21 00:38:42 +04, end at Sun 2018-11-04 01:54:17 +04. --
|
132
|
+
2018-11-04T01:54:15+0400 systemd[1]: Started sample_app-worker.1.service.
|
133
|
+
2018-11-04T01:54:15+0400 systemd[1]: Started sample_app-worker.2.service.
|
134
|
+
2018-11-04T01:54:15+0400 systemd[1]: Started sample_app-web.1.service.
|
135
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: Running in ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
|
136
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: See LICENSE and the LGPL-3.0 for licensing details.
|
137
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
|
138
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.655Z 8826 TID-grcvqfyom INFO: Booting Sidekiq 5.2.2 with redis options {:id=>"Sidekiq-server-PID-8826", :url=>nil}
|
139
|
+
2018-11-04T01:54:17+0400 sample_app-worker.1[8826]: 2018-11-03T21:54:17.658Z 8826 TID-grcvqfyom INFO: Starting processing, hit Ctrl-C to stop
|
140
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.713Z 8827 TID-gniahzm1r INFO: Running in ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
|
141
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.713Z 8827 TID-gniahzm1r INFO: See LICENSE and the LGPL-3.0 for licensing details.
|
142
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.714Z 8827 TID-gniahzm1r INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
|
143
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.714Z 8827 TID-gniahzm1r INFO: Booting Sidekiq 5.2.2 with redis options {:id=>"Sidekiq-server-PID-8827", :url=>nil}
|
144
|
+
2018-11-04T01:54:17+0400 sample_app-worker.2[8827]: 2018-11-03T21:54:17.716Z 8827 TID-gniahzm1r INFO: Starting processing, hit Ctrl-C to stop
|
145
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: => Booting Puma
|
146
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: => Rails 5.2.1 application starting in production
|
147
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: => Run `rails server -h` for more startup options
|
148
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: Puma starting in single mode...
|
149
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Version 3.12.0 (ruby 2.3.0-p0), codename: Llamas in Pajamas
|
150
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Min threads: 5, max threads: 5
|
151
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Environment: production
|
152
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: * Listening on tcp://0.0.0.0:2500
|
153
|
+
2018-11-04T01:54:17+0400 sample_app-web.1[8828]: Use Ctrl-C to stop
|
154
|
+
```
|
155
|
+
|
156
|
+
Systemd provides [a lot of possibilities](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs) to display and manage application logs (`journalctl` command). Procsd supports following options:
|
157
|
+
* `-n` - Specify how many last lines to print. Default is 100
|
158
|
+
* `-t` - Tail, display recent logs and leave the session open for real-time logs to stream in
|
159
|
+
* `--system` - Show only systemd messages about services (start/stop/restart/fail etc.)
|
160
|
+
* `--priority` - Filter messages by a [particular log level.](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs#by-priority) For example show only error messages: `procsd logs --priority err`
|
161
|
+
* `--grep` - [Filter output](https://www.freedesktop.org/software/systemd/man/journalctl.html#-g) to messages where message matches the provided query (may not work for [some](https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1751006) Linux distributions)
|
162
|
+
|
163
|
+
## All available commands
|
164
|
+
|
165
|
+
```
|
166
|
+
$ procsd --help
|
167
|
+
|
168
|
+
Commands:
|
169
|
+
procsd --version, -v # Print the version
|
170
|
+
procsd create d, --dir=$PWD p, --path=$PATH u, --user=$USER # Create and enable app services
|
171
|
+
procsd destroy # Stop, disable and remove app services
|
172
|
+
procsd disable # Disable app target
|
173
|
+
procsd enable # Enable app target
|
174
|
+
procsd help [COMMAND] # Describe available commands or one specific command
|
175
|
+
procsd logs # Show app services logs
|
176
|
+
procsd restart # Restart app services
|
177
|
+
procsd start # Start app services
|
178
|
+
procsd status # Show app services status
|
179
|
+
procsd stop # Stop app services
|
180
|
+
```
|
181
|
+
|
182
|
+
|
183
|
+
## Difference with Foreman
|
184
|
+
|
185
|
+
[Foreman](http://ddollar.github.io/foreman/) itself designed for _development_ (not production) usage only and doing it great. Yes, Foreman allows to [export](http://ddollar.github.io/foreman/#EXPORTING) Procfile to the Systemd, but that's all. After export you have to manually use `systemctl` and `journalctl` to manage/check exported services. Procsd not only exports application, but provides [simple commands](#all-available-commands) to manage exported target.
|
186
|
+
|
187
|
+
There is another difference in the export logic. Foreman systemd export uses [dymamic](https://fedoramagazine.org/systemd-template-unit-files/) services templates and as a result generates a lot of files/folders in the systemd directory even for a simple application.
|
188
|
+
|
189
|
+
Services names contains [$PORT variable](http://ddollar.github.io/foreman/#PROCFILE) at the end. If Procfile has multiple processes, each exported service will have following naming format: `<app_name>-<process_name>@<port_varible_number += 100>.service` (and it's [undocumented](http://ddollar.github.io/foreman/#SYSTEMD-EXPORT) logic). For example for Procfile and formation (_web=1, worker=2_) above, exported services with Foreman will be: `sample_app-web@2500.service`, `sample_app-worker@2600.service` and `sample_app-worker@2601.service`. My opinion about this approach: it's complicated. Why is there PORT variable, which is also incremented for each service? How I'm supposed to remember all these services names to manage or check services status/logs (it's required to provide full names of services for systemctl/journalctl)?
|
190
|
+
|
191
|
+
Procsd following one rule: simplicity. For export it uses static service files (that means for each process will be generated it's own service file) and services names have predictable, Heroku-like names.
|
192
|
+
|
193
|
+
|
194
|
+
## Notes
|
195
|
+
|
196
|
+
* If you want to set environment variables per process, [use format](https://github.com/ddollar/foreman/wiki/Per-Process-Environment-Variables) like Foreman recommends.
|
197
|
+
|
198
|
+
## ToDo
|
199
|
+
* Add Capistrano integration examples
|
200
|
+
* Optional possibility to generate Ngnix config (with out-of-box SSL using [Certbot](https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx)) for an application to use Ngnix as a proxy and serve static files
|
201
|
+
* Add integration with [Inspeqtor](https://github.com/mperham/inspeqtor) to monitor application services and get alert notifications if someting happened
|
202
|
+
|
203
|
+
|
204
|
+
## License
|
205
|
+
|
206
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "procsd"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/procsd
ADDED
data/lib/procsd.rb
ADDED
data/lib/procsd/cli.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative 'generator'
|
3
|
+
|
4
|
+
module Procsd
|
5
|
+
class CLI < Thor
|
6
|
+
class ConfigurationError < StandardError; end
|
7
|
+
map %w[--version -v] => :__print_version
|
8
|
+
|
9
|
+
desc "create", "Create and enable app services"
|
10
|
+
option :user, aliases: :u, type: :string, required: true, banner: "$USER"
|
11
|
+
option :dir, aliases: :d, type: :string, required: true, banner: "$PWD"
|
12
|
+
option :path, aliases: :p, type: :string, required: true, banner: "$PATH"
|
13
|
+
def create
|
14
|
+
preload!
|
15
|
+
|
16
|
+
if !target_exist?
|
17
|
+
gen = Generator.new
|
18
|
+
gen.export!(services, procsd: @procsd, options: options)
|
19
|
+
|
20
|
+
enable
|
21
|
+
if system "sudo", "systemctl", "daemon-reload"
|
22
|
+
say("Reloaded configuraion (daemon-reload)", :green)
|
23
|
+
end
|
24
|
+
|
25
|
+
say("App services were created and enabled. Run `start` to start them", :green)
|
26
|
+
else
|
27
|
+
say("App target `#{target_name}` already exists", :red)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "destroy", "Stop, disable and remove app services"
|
32
|
+
def destroy
|
33
|
+
preload!
|
34
|
+
|
35
|
+
if target_exist?
|
36
|
+
stop
|
37
|
+
disable
|
38
|
+
|
39
|
+
services.keys.push(target_name).each do |filename|
|
40
|
+
path = File.join(systemd_dir, filename)
|
41
|
+
if File.exist? path
|
42
|
+
system "sudo", "rm", path
|
43
|
+
say "Deleted #{path}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if system "sudo", "systemctl", "daemon-reload"
|
48
|
+
say("Reloaded configuraion (daemon-reload)", :green)
|
49
|
+
end
|
50
|
+
|
51
|
+
say("App services were stopped, disabled and removed", :green)
|
52
|
+
else
|
53
|
+
say_target_not_exists
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "enable", "Enable app target"
|
58
|
+
def enable
|
59
|
+
preload!
|
60
|
+
say_target_not_exists and return unless target_exist?
|
61
|
+
|
62
|
+
if target_enabled?
|
63
|
+
say "App target #{target_name} already enabled"
|
64
|
+
else
|
65
|
+
if system "sudo", "systemctl", "enable", target_name
|
66
|
+
say("Enabled app target #{target_name}", :green)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "disable", "Disable app target"
|
72
|
+
def disable
|
73
|
+
preload!
|
74
|
+
say_target_not_exists and return unless target_exist?
|
75
|
+
|
76
|
+
if !target_enabled?
|
77
|
+
say "App target #{target_name} already disabled"
|
78
|
+
else
|
79
|
+
if system "sudo", "systemctl", "disable", target_name
|
80
|
+
say("Disabled app target #{target_name}", :green)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "start", "Start app services"
|
86
|
+
def start
|
87
|
+
preload!
|
88
|
+
say_target_not_exists and return unless target_exist?
|
89
|
+
|
90
|
+
if target_active?
|
91
|
+
say "Already started/active (#{target_name})"
|
92
|
+
else
|
93
|
+
if system "sudo", "systemctl", "start", target_name
|
94
|
+
say("Started app services (#{target_name})", :green)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
desc "stop", "Stop app services"
|
100
|
+
def stop
|
101
|
+
preload!
|
102
|
+
say_target_not_exists and return unless target_exist?
|
103
|
+
|
104
|
+
if !target_active?
|
105
|
+
say "Already stopped/inactive (#{target_name})"
|
106
|
+
else
|
107
|
+
if system "sudo", "systemctl", "stop", target_name
|
108
|
+
say("Stopped app services (#{target_name})", :green)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "restart", "Restart app services"
|
114
|
+
def restart
|
115
|
+
preload!
|
116
|
+
say_target_not_exists and return unless target_exist?
|
117
|
+
|
118
|
+
if system "sudo", "systemctl", "restart", target_name
|
119
|
+
say("Restarted app services (#{target_name})", :green)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "status", "Show app services status"
|
124
|
+
option :target, type: :string, banner: "Show main target status"
|
125
|
+
def status(service_name = nil)
|
126
|
+
preload!
|
127
|
+
say_target_not_exists and return unless target_exist?
|
128
|
+
|
129
|
+
command = %w(systemctl status --no-pager --output short-iso)
|
130
|
+
if options["target"]
|
131
|
+
command << target_name
|
132
|
+
else
|
133
|
+
filtered = filter_services(service_name)
|
134
|
+
say("Can't find any services matching given name: #{service_name}", :red) and return if filtered.empty?
|
135
|
+
command += filtered
|
136
|
+
end
|
137
|
+
|
138
|
+
system *command
|
139
|
+
end
|
140
|
+
|
141
|
+
desc "logs", "Show app services logs"
|
142
|
+
option :num, aliases: :n, type: :string, banner: "How many lines to print"
|
143
|
+
option :tail, aliases: [:t, :f], type: :boolean, banner: "Display logs in real-time"
|
144
|
+
option :system, type: :boolean, banner: "Show only system messages"
|
145
|
+
option :priority, aliases: :p, type: :string, banner: "Show messages with a particular log level"
|
146
|
+
option :grep, aliases: :g, type: :string, banner: "Filter output to entries where message matches the provided query"
|
147
|
+
def logs(service_name = nil)
|
148
|
+
preload!
|
149
|
+
|
150
|
+
command = %w(journalctl --no-pager --all --no-hostname --output short-iso)
|
151
|
+
command.push("-n", options.fetch("num", "100"))
|
152
|
+
command.push("-f") if options["tail"]
|
153
|
+
command.push("--system") if options["system"]
|
154
|
+
command.push("--priority", options["priority"]) if options["priority"]
|
155
|
+
command.push("--grep", "'" + options["grep"] + "'") if options["grep"]
|
156
|
+
|
157
|
+
filtered = filter_services(service_name)
|
158
|
+
say("Can't find any services matching given name: #{service_name}", :red) and return if filtered.empty?
|
159
|
+
|
160
|
+
filtered.each { |service| command.push("--unit", service) }
|
161
|
+
system *command
|
162
|
+
end
|
163
|
+
|
164
|
+
desc "--version, -v", "Print the version"
|
165
|
+
def __print_version
|
166
|
+
puts VERSION
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def say_target_not_exists
|
172
|
+
say("App target #{target_name} is not exists", :red)
|
173
|
+
end
|
174
|
+
|
175
|
+
def filter_services(service_name)
|
176
|
+
if service_name
|
177
|
+
services.keys.select { |s| s.include?("#{app_name}-#{service_name}") }
|
178
|
+
else
|
179
|
+
services.keys
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def target_exist?
|
184
|
+
File.exist?(File.join systemd_dir, target_name)
|
185
|
+
end
|
186
|
+
|
187
|
+
def systemd_dir
|
188
|
+
@procsd["systemd_dir"]
|
189
|
+
end
|
190
|
+
|
191
|
+
def target_enabled?
|
192
|
+
system "systemctl", "is-enabled", "--quiet", target_name
|
193
|
+
end
|
194
|
+
|
195
|
+
def target_active?
|
196
|
+
system "systemctl", "is-active", "--quiet", target_name
|
197
|
+
end
|
198
|
+
|
199
|
+
def target_name
|
200
|
+
"#{app_name}.target"
|
201
|
+
end
|
202
|
+
|
203
|
+
def app_name
|
204
|
+
@procsd["app"]
|
205
|
+
end
|
206
|
+
|
207
|
+
def services
|
208
|
+
all = {}
|
209
|
+
@procfile.each do |process_name, process_command|
|
210
|
+
processes_count = @procsd["formation"][process_name] || 1
|
211
|
+
processes_count.times do |i|
|
212
|
+
all["#{app_name}-#{process_name}.#{i + 1}.service"] = process_command
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
all
|
217
|
+
end
|
218
|
+
|
219
|
+
def preload!
|
220
|
+
raise ConfigurationError, "Procfile file doesn't exists" unless File.exist? "Procfile"
|
221
|
+
raise ConfigurationError, ".procsd.yml config file doesn't exists" unless File.exist? ".procsd.yml"
|
222
|
+
|
223
|
+
@procfile = YAML.load_file("Procfile")
|
224
|
+
@procsd = YAML.load_file(".procsd.yml")
|
225
|
+
raise ConfigurationError, "Missing app name in the .procsd.yml file" unless @procsd["app"]
|
226
|
+
|
227
|
+
if formation = @procsd["formation"]
|
228
|
+
@procsd["formation"] = formation.split(",").map { |f| f.split("=") }.to_h
|
229
|
+
@procsd["formation"].each { |k, v| @procsd["formation"][k] = v.to_i }
|
230
|
+
else
|
231
|
+
@procsd["formation"] = {}
|
232
|
+
end
|
233
|
+
|
234
|
+
@procsd["environment"] ||= []
|
235
|
+
@procsd["systemd_dir"] ||= DEFAULT_SYSTEMD_DIR
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Procsd
|
2
|
+
class Generator < Thor::Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
def self.source_root
|
6
|
+
File.dirname(__FILE__)
|
7
|
+
end
|
8
|
+
|
9
|
+
def export!(services, procsd:, options:)
|
10
|
+
self.destination_root = "/tmp"
|
11
|
+
@procsd = procsd
|
12
|
+
say "Systemd directory: #{@procsd["systemd_dir"]}"
|
13
|
+
|
14
|
+
app_name = @procsd["app"]
|
15
|
+
target_name = "#{app_name}.target"
|
16
|
+
|
17
|
+
services.each do |service_name, service_command|
|
18
|
+
service_config = options.merge(
|
19
|
+
"target_name" => target_name,
|
20
|
+
"id" => service_name.sub(".service", ""),
|
21
|
+
"command" => service_command,
|
22
|
+
"environment" => @procsd["environment"]
|
23
|
+
)
|
24
|
+
generate(service_name, service_config, type: :service)
|
25
|
+
end
|
26
|
+
|
27
|
+
target_config = {
|
28
|
+
"app" => app_name,
|
29
|
+
"services" => services.keys
|
30
|
+
}
|
31
|
+
generate(target_name, target_config, type: :target)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def generate(filename, confing, type:)
|
37
|
+
template("templates/#{type}.erb", filename, confing)
|
38
|
+
|
39
|
+
source_path = File.join(destination_root, filename)
|
40
|
+
dest_path = File.join(@procsd["systemd_dir"], filename)
|
41
|
+
system "sudo", "mv", source_path, dest_path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
[Unit]
|
2
|
+
Requires=network.target
|
3
|
+
PartOf=<%= config["target_name"] %>
|
4
|
+
|
5
|
+
[Service]
|
6
|
+
Type=simple
|
7
|
+
User=<%= config["user"] %>
|
8
|
+
WorkingDirectory=<%= config["dir"] %>
|
9
|
+
ExecStart=/bin/bash -lc '<%= config["command"] %>'
|
10
|
+
|
11
|
+
Restart=always
|
12
|
+
RestartSec=1
|
13
|
+
TimeoutStopSec=15
|
14
|
+
KillMode=mixed
|
15
|
+
StandardInput=null
|
16
|
+
SyslogIdentifier=<%= config["id"] %>
|
17
|
+
|
18
|
+
Environment="PATH=<%= config["path"] %>"
|
19
|
+
<% config["environment"].each do |env| -%>
|
20
|
+
Environment="<%= env %>"
|
21
|
+
<% end -%>
|
data/procsd.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "procsd/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "procsd"
|
8
|
+
spec.version = Procsd::VERSION
|
9
|
+
spec.authors = ["Victor Afanasev"]
|
10
|
+
spec.email = ["vicfreefly@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Manage your application processes in production hassle-free like Heroku CLI with Procfile and Systemd"
|
13
|
+
spec.homepage = "https://github.com/vifreefly/procsd"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
|
22
|
+
spec.bindir = "exe"
|
23
|
+
spec.executables = "procsd"
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
spec.required_ruby_version = ">= 2.3.0"
|
26
|
+
|
27
|
+
spec.add_dependency "thor"
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: procsd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Victor Afanasev
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- vicfreefly@gmail.com
|
72
|
+
executables:
|
73
|
+
- procsd
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- exe/procsd
|
85
|
+
- lib/procsd.rb
|
86
|
+
- lib/procsd/cli.rb
|
87
|
+
- lib/procsd/generator.rb
|
88
|
+
- lib/procsd/templates/service.erb
|
89
|
+
- lib/procsd/templates/target.erb
|
90
|
+
- lib/procsd/version.rb
|
91
|
+
- procsd.gemspec
|
92
|
+
homepage: https://github.com/vifreefly/procsd
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 2.3.0
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.7.6
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Manage your application processes in production hassle-free like Heroku CLI
|
116
|
+
with Procfile and Systemd
|
117
|
+
test_files: []
|