procsd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in procsd.gemspec
6
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/procsd ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "procsd"
4
+ require "procsd/cli"
5
+
6
+ Procsd::CLI.start(ARGV)
data/lib/procsd.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'thor'
2
+ require 'procsd/version'
3
+
4
+ module Procsd
5
+ DEFAULT_SYSTEMD_DIR = "/etc/systemd/system".freeze
6
+ end
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 -%>
@@ -0,0 +1,6 @@
1
+ [Unit]
2
+ Description=<%= config["app"] %> target
3
+ Wants=<%= config["services"].join(" ") %>
4
+
5
+ [Install]
6
+ WantedBy=multi-user.target
@@ -0,0 +1,3 @@
1
+ module Procsd
2
+ VERSION = "0.1.0"
3
+ 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: []