procsd 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +47 -27
- data/lib/procsd.rb +1 -0
- data/lib/procsd/cli.rb +161 -86
- data/lib/procsd/generator.rb +11 -8
- data/lib/procsd/templates/service.erb +2 -2
- data/lib/procsd/templates/target.erb +0 -1
- data/lib/procsd/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a029c6f4d0953cc9b21f3508a14e9057a4386fd53c786167737a9097c4175f13
|
4
|
+
data.tar.gz: 2f3fa1f897cb3bbf65aa6e00c1b8fbc0c3f1bde13d5dc559514633404ef691ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f5a266a604bdbc1cbd9519b510a1eabf801d753d8497729a7ed64c2acedcfd6d1873f16da89896fedb5e49467f7c987c09e53ddef6d2d1ff947cbd07501173e
|
7
|
+
data.tar.gz: 8724b65f9e834e5654690148a0c21444608add28c60df412dd76d47ad18f2c2edbef20ae8029de39b2431a2c6585531656e7b4a629d0427f71e717c4bd314d8b
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,30 @@
|
|
1
1
|
# CHANGELOG
|
2
|
+
## 0.3.0
|
3
|
+
* **Breaking change:** `.procsd.yml` renamed to `procsd.yml` (without dot)
|
4
|
+
* **Breaking change:** `environment` option in the procsd.yml now has hash format, not array:
|
5
|
+
|
6
|
+
Was:
|
7
|
+
```
|
8
|
+
environment:
|
9
|
+
- PORT=2501
|
10
|
+
- RAILS_ENV=production
|
11
|
+
- RAILS_LOG_TO_STDOUT=true
|
12
|
+
```
|
13
|
+
|
14
|
+
Now:
|
15
|
+
```
|
16
|
+
environment:
|
17
|
+
PORT: 2501
|
18
|
+
RAILS_ENV: production
|
19
|
+
RAILS_LOG_TO_STDOUT: true
|
20
|
+
```
|
21
|
+
|
22
|
+
* Add `--or-restart` option for `create` command
|
23
|
+
* Options `--user`, `--path` and `--dir` for `create` command are not required anymore (but still can be provided)
|
24
|
+
* Add new `config` command (currently it can print only content for sudoers file: `$ procsd config sudoers`)
|
25
|
+
* Add `--add-to-sudoers` option for `create` command to automatically add `/bin/systemctl start/stop/restart app_name.target` commands to sudoers `/etc/sudoers.d/app_name` file (passwordless sudo)
|
26
|
+
|
27
|
+
|
2
28
|
## 0.2.0
|
3
29
|
* Allow to use erb inside .procsd.yml
|
4
30
|
* Add dotenv support
|
data/README.md
CHANGED
@@ -8,6 +8,8 @@ Can we have something similar on the cheap Ubuntu VPS from DigitalOcean? Yes we
|
|
8
8
|
|
9
9
|
## Getting started
|
10
10
|
|
11
|
+
**Note:** latest version of Procsd is `0.3.0`. Since version `0.2.0` there are some breaking changes. Check the [CHANGELOG.md](CHANGELOG.md). Run `$ gem update procsd` or `$ bundle update procsd` (if you have already installed procsd) to update to the latest version.
|
12
|
+
|
11
13
|
> Install `procsd` first: `$ gem install procsd`. Required Ruby version is `>= 2.3.0`.
|
12
14
|
|
13
15
|
Let's say you have following application's Procfile:
|
@@ -16,17 +18,18 @@ Let's say you have following application's Procfile:
|
|
16
18
|
web: bundle exec rails server -p $PORT
|
17
19
|
worker: bundle exec sidekiq -e $RAILS_ENV
|
18
20
|
```
|
19
|
-
and you want to have one instance of web process && two instances of worker process. Create inside application directory
|
21
|
+
and you want to have one instance of web process && two instances of worker process. Create inside application directory `procsd.yml` config file:
|
20
22
|
|
21
23
|
```yaml
|
22
24
|
app: sample_app
|
23
25
|
formation: web=1,worker=2
|
24
26
|
environment:
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
PORT: 2501
|
28
|
+
RAILS_ENV: production
|
29
|
+
RAILS_LOG_TO_STDOUT: true
|
28
30
|
```
|
29
|
-
|
31
|
+
|
32
|
+
> The only required option in `procsd.yml` is `app` (application name). Also you can provide custom Systemd directory path (`systemd_dir` option, default is _/etc/systemd/system_)
|
30
33
|
|
31
34
|
Configuration is done.
|
32
35
|
|
@@ -36,9 +39,13 @@ Configuration is done.
|
|
36
39
|
> 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
40
|
|
38
41
|
```
|
39
|
-
deploy@server:~/sample_app$ procsd create
|
42
|
+
deploy@server:~/sample_app$ procsd create
|
40
43
|
|
44
|
+
Value of the --user option: deploy
|
45
|
+
Value of the --dir option: /home/deploy/sample_app
|
46
|
+
Value of the --path option: /home/deploy/.rbenv/shims:/home/deploy/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
|
41
47
|
Systemd directory: /etc/systemd/system
|
48
|
+
|
42
49
|
create sample_app-web.1.service
|
43
50
|
create sample_app-worker.1.service
|
44
51
|
create sample_app-worker.2.service
|
@@ -47,8 +54,19 @@ Created symlink /etc/systemd/system/multi-user.target.wants/sample_app.target
|
|
47
54
|
Enabled app target sample_app.target
|
48
55
|
Reloaded configuraion (daemon-reload)
|
49
56
|
App services were created and enabled. Run `start` to start them
|
57
|
+
|
58
|
+
Note: add following line to the sudoers file (`$ sudo visudo`) if you don't want to type password each time for start/stop/restart commands:
|
59
|
+
deploy ALL=NOPASSWD: /bin/systemctl start sample_app.target, /bin/systemctl stop sample_app.target, /bin/systemctl restart sample_app.target
|
50
60
|
```
|
51
61
|
|
62
|
+
You can provide additional options for `create` command:
|
63
|
+
* `--user` - name of the user, default is current _$USER_ env variable
|
64
|
+
* `--dir` - application's working directory, default is current _$PWD_ env variable
|
65
|
+
* `--path` - $PATH to include to the each service. Default is current _$PATH_ env variable
|
66
|
+
* `--add-to-sudoers` - if option present, procsd will create sudoers rule `/etc/sudoers.d/app_name` allowing to start/stop/restart app services without password prompt.
|
67
|
+
* `--or-restart` - if option present and servides already created, procsd will skip creation and instead call `restart` command
|
68
|
+
|
69
|
+
|
52
70
|
### Start application
|
53
71
|
> Other control commands: `stop` and `restart`
|
54
72
|
|
@@ -120,7 +138,7 @@ deploy@server:~/sample_app$ procsd status
|
|
120
138
|
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
|
121
139
|
```
|
122
140
|
|
123
|
-
Also you can see status in
|
141
|
+
Also you can see status in short format:
|
124
142
|
|
125
143
|
```
|
126
144
|
deploy@server:~/sample_app$ procsd status --short
|
@@ -175,18 +193,19 @@ Systemd provides [a lot of possibilities](https://www.digitalocean.com/community
|
|
175
193
|
$ procsd --help
|
176
194
|
|
177
195
|
Commands:
|
178
|
-
procsd --version, -v
|
179
|
-
procsd
|
180
|
-
procsd
|
181
|
-
procsd
|
182
|
-
procsd
|
183
|
-
procsd
|
184
|
-
procsd
|
185
|
-
procsd
|
186
|
-
procsd
|
187
|
-
procsd
|
188
|
-
procsd
|
189
|
-
procsd
|
196
|
+
procsd --version, -v # Print the version
|
197
|
+
procsd config # Show configuration. Available types: sudoers
|
198
|
+
procsd create # Create and enable app services
|
199
|
+
procsd destroy # Stop, disable and remove app services
|
200
|
+
procsd disable # Disable app target
|
201
|
+
procsd enable # Enable app target
|
202
|
+
procsd help [COMMAND] # Describe available commands or one specific command
|
203
|
+
procsd list # List all app services
|
204
|
+
procsd logs # Show app services logs
|
205
|
+
procsd restart # Restart app services
|
206
|
+
procsd start # Start app services
|
207
|
+
procsd status # Show app services status
|
208
|
+
procsd stop # Stop app services
|
190
209
|
```
|
191
210
|
|
192
211
|
|
@@ -209,30 +228,31 @@ Procsd following one rule: simplicity. For export it uses static service files (
|
|
209
228
|
```
|
210
229
|
deploy@server:~/sample_app$ VERBOSE=true procsd logs -n 3
|
211
230
|
|
212
|
-
> Executing command: journalctl --no-pager --
|
231
|
+
> Executing command: `journalctl --no-pager --no-hostname --all --output short-iso -n 3 --unit sample_app-*`
|
213
232
|
|
214
233
|
-- Logs begin at Sun 2018-10-21 00:38:42 +04, end at Sun 2018-11-04 19:17:01 +04. --
|
215
234
|
2018-11-04T19:11:59+0400 sample_app-worker.2[29907]: 2018-11-04T15:11:59.597Z 29907 TID-gne5aeyuz INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
|
216
235
|
2018-11-04T19:11:59+0400 sample_app-worker.2[29907]: 2018-11-04T15:11:59.597Z 29907 TID-gne5aeyuz INFO: Booting Sidekiq 5.2.2 with redis options {:id=>"Sidekiq-server-PID-29907", :url=>nil}
|
217
236
|
2018-11-04T19:11:59+0400 sample_app-worker.2[29907]: 2018-11-04T15:11:59.601Z 29907 TID-gne5aeyuz INFO: Starting processing, hit Ctrl-C to stop
|
218
237
|
```
|
219
|
-
* You can use extended format of
|
220
|
-
> Keep in mind that syntax below is not supported by Foreman or Heroku
|
238
|
+
* You can use extended format of processes commands inside `procsd.yml` to provide additional restart/stop commands for processes:
|
221
239
|
|
222
240
|
> All possible options: `start`, `restart` and `stop`
|
241
|
+
> If procsd.yml has `processes:` option defined, then content of Procfile will be ignored
|
223
242
|
|
224
243
|
```yml
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
244
|
+
processes:
|
245
|
+
web:
|
246
|
+
start: bundle exec rails server -p $PORT
|
247
|
+
restart: bundle exec pumactl phased-restart
|
248
|
+
worker: bundle exec sidekiq -e production
|
249
|
+
app: sample_app
|
229
250
|
```
|
230
251
|
|
231
252
|
Why? For example default Ruby on Rails application server [Puma](http://puma.io/) supports [Phased or Rolling restart](https://github.com/puma/puma/blob/master/docs/restart.md#normal-vs-hot-vs-phased-restart) feature. If you provide separate `restart`command for a process, then this command will be called (`$ procsd restart`) by Systemd instead of just killing and starting process again.
|
232
253
|
|
233
254
|
|
234
255
|
## ToDo
|
235
|
-
* Add Capistrano integration examples
|
236
256
|
* 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
|
237
257
|
* Add integration with [Inspeqtor](https://github.com/mperham/inspeqtor) to monitor application services and get alert notifications if something happened
|
238
258
|
|
data/lib/procsd.rb
CHANGED
data/lib/procsd/cli.rb
CHANGED
@@ -5,27 +5,73 @@ require_relative 'generator'
|
|
5
5
|
module Procsd
|
6
6
|
class CLI < Thor
|
7
7
|
class ConfigurationError < StandardError; end
|
8
|
+
class ArgumentError < StandardError; end
|
8
9
|
map %w[--version -v] => :__print_version
|
9
10
|
|
10
11
|
desc "create", "Create and enable app services"
|
11
|
-
option :user, aliases: :u, type: :string,
|
12
|
-
option :dir, aliases: :d, type: :string,
|
13
|
-
option :path, aliases: :p, type: :string,
|
12
|
+
option :user, aliases: :u, type: :string, banner: "$USER"
|
13
|
+
option :dir, aliases: :d, type: :string, banner: "$PWD"
|
14
|
+
option :path, aliases: :p, type: :string, banner: "$PATH"
|
15
|
+
option :'or-restart', type: :boolean, banner: "Create and start app services if not created yet, otherwise restart"
|
16
|
+
option :'add-to-sudoers', type: :boolean, banner: "Create sudoers rule at /etc/sudoers.d/app_name to allow manage app target without password prompt"
|
14
17
|
def create
|
15
18
|
preload!
|
16
19
|
|
17
20
|
if !target_exist?
|
21
|
+
opts = {
|
22
|
+
user: options["user"] || ENV["USER"],
|
23
|
+
dir: options["dir"] || ENV["PWD"],
|
24
|
+
path: options["path"] || fetch_path_env
|
25
|
+
}
|
26
|
+
|
27
|
+
opts.each do |key, value|
|
28
|
+
if value.nil? || value.empty?
|
29
|
+
say("Can't fetch value for --#{key}, please provide it as an argument", :red) and return
|
30
|
+
else
|
31
|
+
say "Value of the --#{key} option: #{value}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
18
35
|
gen = Generator.new
|
19
|
-
gen.export!(services,
|
36
|
+
gen.export!(services, config: @config, options: options.merge(opts))
|
20
37
|
|
21
38
|
enable
|
22
39
|
if execute %w(sudo systemctl daemon-reload)
|
23
40
|
say("Reloaded configuraion (daemon-reload)", :green)
|
24
41
|
end
|
25
42
|
|
26
|
-
|
43
|
+
if options["or-restart"]
|
44
|
+
start
|
45
|
+
say("App services were created, enabled and started", :green)
|
46
|
+
else
|
47
|
+
say("App services were created and enabled. Run `start` to start them", :green)
|
48
|
+
end
|
49
|
+
|
50
|
+
sudoers_rule_content = generate_sudoers_rule(opts[:user])
|
51
|
+
if options["add-to-sudoers"]
|
52
|
+
sudoers_file_temp_path = "/tmp/#{app_name}"
|
53
|
+
sudoers_file_dest_path = "#{SUDOERS_DIR}/#{app_name}"
|
54
|
+
if Dir.exist?(SUDOERS_DIR)
|
55
|
+
File.open(sudoers_file_temp_path, "w") { |f| f.puts sudoers_rule_content }
|
56
|
+
execute %W(sudo chown root:root #{sudoers_file_temp_path})
|
57
|
+
execute %W(sudo chmod 0440 #{sudoers_file_temp_path})
|
58
|
+
if execute %W(sudo mv #{sudoers_file_temp_path} #{sudoers_file_dest_path})
|
59
|
+
say("Sudoers file #{sudoers_file_dest_path} was created", :green)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
say "Directory #{SUDOERS_DIR} does not exist, sudoers file wan't created"
|
63
|
+
end
|
64
|
+
else
|
65
|
+
say "Note: add following line to the sudoers file (`$ sudo visudo`) if you don't " \
|
66
|
+
"want to type password each time for start/stop/restart commands:"
|
67
|
+
puts sudoers_rule_content
|
68
|
+
end
|
27
69
|
else
|
28
|
-
|
70
|
+
if options["or-restart"]
|
71
|
+
restart
|
72
|
+
else
|
73
|
+
say("App target `#{target_name}` already exists", :red)
|
74
|
+
end
|
29
75
|
end
|
30
76
|
end
|
31
77
|
|
@@ -39,17 +85,20 @@ module Procsd
|
|
39
85
|
|
40
86
|
services.keys.push(target_name).each do |filename|
|
41
87
|
path = File.join(systemd_dir, filename)
|
42
|
-
if File.exist? path
|
43
|
-
execute %W(sudo rm #{path})
|
44
|
-
say "Deleted #{path}"
|
45
|
-
end
|
88
|
+
execute %W(sudo rm #{path}) and say "Deleted #{path}" if File.exist? path
|
46
89
|
end
|
47
90
|
|
48
91
|
if execute %w(sudo systemctl daemon-reload)
|
49
92
|
say("Reloaded configuraion (daemon-reload)", :green)
|
50
93
|
end
|
51
|
-
|
52
94
|
say("App services were stopped, disabled and removed", :green)
|
95
|
+
|
96
|
+
sudoers_file_path = "#{SUDOERS_DIR}/#{app_name}"
|
97
|
+
if File.exist?(sudoers_file_path)
|
98
|
+
if yes?("Remove sudoers rule #{sudoers_file_path} ? (yes/no)")
|
99
|
+
say("Sudoers file removed", :green) if execute %W(sudo rm #{sudoers_file_path})
|
100
|
+
end
|
101
|
+
end
|
53
102
|
else
|
54
103
|
say_target_not_exists
|
55
104
|
end
|
@@ -60,12 +109,9 @@ module Procsd
|
|
60
109
|
preload!
|
61
110
|
say_target_not_exists and return unless target_exist?
|
62
111
|
|
63
|
-
if target_enabled?
|
64
|
-
|
65
|
-
|
66
|
-
if execute %W(sudo systemctl enable #{target_name})
|
67
|
-
say("Enabled app target #{target_name}", :green)
|
68
|
-
end
|
112
|
+
say "Note: app target #{target_name} already enabled" if target_enabled?
|
113
|
+
if execute %W(sudo systemctl enable #{target_name})
|
114
|
+
say("Enabled app target #{target_name}", :green)
|
69
115
|
end
|
70
116
|
end
|
71
117
|
|
@@ -74,12 +120,9 @@ module Procsd
|
|
74
120
|
preload!
|
75
121
|
say_target_not_exists and return unless target_exist?
|
76
122
|
|
77
|
-
if !target_enabled?
|
78
|
-
|
79
|
-
|
80
|
-
if execute %W(sudo systemctl disable #{target_name})
|
81
|
-
say("Disabled app target #{target_name}", :green)
|
82
|
-
end
|
123
|
+
say "Note: app target #{target_name} already disabled" if !target_enabled?
|
124
|
+
if execute %W(sudo systemctl disable #{target_name})
|
125
|
+
say("Disabled app target #{target_name}", :green)
|
83
126
|
end
|
84
127
|
end
|
85
128
|
|
@@ -88,12 +131,9 @@ module Procsd
|
|
88
131
|
preload!
|
89
132
|
say_target_not_exists and return unless target_exist?
|
90
133
|
|
91
|
-
if target_active?
|
92
|
-
|
93
|
-
|
94
|
-
if execute %W(sudo systemctl start #{target_name})
|
95
|
-
say("Started app services (#{target_name})", :green)
|
96
|
-
end
|
134
|
+
say "Note: app target #{target_name} already started/active" if target_active?
|
135
|
+
if execute %W(sudo systemctl start #{target_name})
|
136
|
+
say("Started app services (#{target_name})", :green)
|
97
137
|
end
|
98
138
|
end
|
99
139
|
|
@@ -102,12 +142,9 @@ module Procsd
|
|
102
142
|
preload!
|
103
143
|
say_target_not_exists and return unless target_exist?
|
104
144
|
|
105
|
-
if !target_active?
|
106
|
-
|
107
|
-
|
108
|
-
if execute %W(sudo systemctl stop #{target_name})
|
109
|
-
say("Stopped app services (#{target_name})", :green)
|
110
|
-
end
|
145
|
+
say "Note: app target #{target_name} already stopped/inactive" if !target_active?
|
146
|
+
if execute %W(sudo systemctl stop #{target_name})
|
147
|
+
say("Stopped app services (#{target_name})", :green)
|
111
148
|
end
|
112
149
|
end
|
113
150
|
|
@@ -118,10 +155,10 @@ module Procsd
|
|
118
155
|
|
119
156
|
# If one of the child services of a target has `ExecReload` and `ReloadPropagatedFrom`
|
120
157
|
# options defined, then use `reload-or-restart` to call all services (not the main target)
|
121
|
-
# because https://github.com/systemd/systemd/issues/10638
|
158
|
+
# because of systemd bug https://github.com/systemd/systemd/issues/10638
|
122
159
|
success =
|
123
|
-
if
|
124
|
-
execute %
|
160
|
+
if has_reload?
|
161
|
+
execute %W(sudo systemctl reload-or-restart #{app_name}-* --all)
|
125
162
|
else
|
126
163
|
execute %W(sudo systemctl restart #{target_name})
|
127
164
|
end
|
@@ -139,19 +176,12 @@ module Procsd
|
|
139
176
|
say_target_not_exists and return unless target_exist?
|
140
177
|
|
141
178
|
if options["short"]
|
142
|
-
command = %w(
|
179
|
+
command = %w(systemctl list-units --no-pager --no-legend --all)
|
143
180
|
else
|
144
|
-
command = %w(
|
145
|
-
end
|
146
|
-
|
147
|
-
if options["target"]
|
148
|
-
command << target_name
|
149
|
-
else
|
150
|
-
filtered = filter_services(service_name)
|
151
|
-
say("Can't find any services matching given name: #{service_name}", :red) and return if filtered.empty?
|
152
|
-
command += filtered
|
181
|
+
command = %w(systemctl status --no-pager --output short-iso --all)
|
153
182
|
end
|
154
183
|
|
184
|
+
command << (options["target"] ? target_name : "#{app_name}-#{service_name}*")
|
155
185
|
execute command
|
156
186
|
end
|
157
187
|
|
@@ -164,17 +194,14 @@ module Procsd
|
|
164
194
|
def logs(service_name = nil)
|
165
195
|
preload!
|
166
196
|
|
167
|
-
command = %w(
|
197
|
+
command = %w(journalctl --no-pager --no-hostname --all --output short-iso)
|
168
198
|
command.push("-n", options.fetch("num", "100"))
|
169
199
|
command.push("-f") if options["tail"]
|
170
200
|
command.push("--system") if options["system"]
|
171
201
|
command.push("--priority", options["priority"]) if options["priority"]
|
172
202
|
command.push("--grep", "'" + options["grep"] + "'") if options["grep"]
|
173
203
|
|
174
|
-
|
175
|
-
say("Can't find any services matching given name: #{service_name}", :red) and return if filtered.empty?
|
176
|
-
|
177
|
-
filtered.each { |service| command.push("--unit", service) }
|
204
|
+
command.push("--unit", "#{app_name}-#{service_name}*")
|
178
205
|
execute command
|
179
206
|
end
|
180
207
|
|
@@ -183,7 +210,7 @@ module Procsd
|
|
183
210
|
preload!
|
184
211
|
say_target_not_exists and return unless target_exist?
|
185
212
|
|
186
|
-
execute %W(
|
213
|
+
execute %W(systemctl list-dependencies #{target_name})
|
187
214
|
end
|
188
215
|
|
189
216
|
desc "--version, -v", "Print the version"
|
@@ -191,9 +218,42 @@ module Procsd
|
|
191
218
|
puts VERSION
|
192
219
|
end
|
193
220
|
|
221
|
+
desc "config", "Show configuration. Available types: sudoers"
|
222
|
+
def config(name)
|
223
|
+
preload!
|
224
|
+
|
225
|
+
case name
|
226
|
+
when "sudoers"
|
227
|
+
say generate_sudoers_rule(ENV["USER"])
|
228
|
+
else
|
229
|
+
raise ArgumentError, "Wring type of argument: #{name}"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
194
233
|
private
|
195
234
|
|
235
|
+
def generate_sudoers_rule(user)
|
236
|
+
commands = []
|
237
|
+
systemctl_path = `which systemctl`.strip
|
238
|
+
|
239
|
+
%w(start stop restart).each { |cmd| commands << "#{systemctl_path} #{cmd} #{target_name}" }
|
240
|
+
commands << "#{systemctl_path} reload-or-restart #{app_name}-\\* --all" if has_reload?
|
241
|
+
|
242
|
+
"#{user} ALL=NOPASSWD: #{commands.join(', ')}"
|
243
|
+
end
|
244
|
+
|
245
|
+
def has_reload?
|
246
|
+
services.any? { |_, opts| opts["restart"] }
|
247
|
+
end
|
248
|
+
|
249
|
+
def fetch_path_env
|
250
|
+
# get value of the $PATH env variable including ~/.bashrc as well (-i flag)
|
251
|
+
`/bin/bash -ilc 'echo $PATH'`.strip
|
252
|
+
end
|
253
|
+
|
196
254
|
def execute(command)
|
255
|
+
trap("INT") { puts "\nInterrupted" ; exit 130 }
|
256
|
+
|
197
257
|
say("> Executing command: `#{command.join(' ')}`", :yellow) if ENV["VERBOSE"] == "true"
|
198
258
|
system *command
|
199
259
|
end
|
@@ -202,20 +262,12 @@ module Procsd
|
|
202
262
|
say("App target #{target_name} is not exists", :red)
|
203
263
|
end
|
204
264
|
|
205
|
-
def filter_services(service_name)
|
206
|
-
if service_name
|
207
|
-
services.keys.select { |s| s.include?("#{app_name}-#{service_name}") }
|
208
|
-
else
|
209
|
-
services.keys
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
265
|
def target_exist?
|
214
266
|
File.exist?(File.join systemd_dir, target_name)
|
215
267
|
end
|
216
268
|
|
217
269
|
def systemd_dir
|
218
|
-
@
|
270
|
+
@config[:systemd_dir]
|
219
271
|
end
|
220
272
|
|
221
273
|
def target_enabled?
|
@@ -231,15 +283,15 @@ module Procsd
|
|
231
283
|
end
|
232
284
|
|
233
285
|
def app_name
|
234
|
-
@
|
286
|
+
@config[:app]
|
235
287
|
end
|
236
288
|
|
237
289
|
def services
|
238
290
|
all = {}
|
239
|
-
@
|
240
|
-
|
241
|
-
|
242
|
-
all["#{app_name}-#{process_name}.#{i + 1}.service"] =
|
291
|
+
@config[:processes].each do |process_name, opts|
|
292
|
+
opts["count"].times do |i|
|
293
|
+
commands = { "start" => opts["start"], "stop" => opts["stop"], "restart" => opts["restart"] }
|
294
|
+
all["#{app_name}-#{process_name}.#{i + 1}.service"] = commands
|
243
295
|
end
|
244
296
|
end
|
245
297
|
|
@@ -247,32 +299,55 @@ module Procsd
|
|
247
299
|
end
|
248
300
|
|
249
301
|
def preload!
|
250
|
-
|
251
|
-
raise ConfigurationError, ".procsd.yml config file doesn't exists" unless File.exist? ".procsd.yml"
|
302
|
+
@config = {}
|
252
303
|
|
253
|
-
|
254
|
-
|
255
|
-
|
304
|
+
unless system("which", "systemctl", [:out, :err]=>"/dev/null")
|
305
|
+
raise ConfigurationError, "Your OS doesn't has systemctl executable available"
|
306
|
+
end
|
307
|
+
raise ConfigurationError, "Config file procsd.yml doesn't exists" unless File.exist? "procsd.yml"
|
308
|
+
begin
|
309
|
+
procsd = YAML.load(ERB.new(File.read "procsd.yml").result)
|
310
|
+
rescue => e
|
311
|
+
raise ConfigurationError, "Can't read procsd.yml: #{e.inspect}"
|
312
|
+
end
|
256
313
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
314
|
+
raise ConfigurationError, "Missing app name in the procsd.yml file" unless procsd["app"]
|
315
|
+
@config[:app] = procsd["app"]
|
316
|
+
|
317
|
+
# If procsd.yml doesn't contains processes defined, try to read Procfile
|
318
|
+
unless procsd["processes"]
|
319
|
+
msg = "Procfile doesn't exists. Define processes in procsd.yml or create Procfile"
|
320
|
+
raise ConfigurationError, msg unless File.exist? "Procfile"
|
321
|
+
begin
|
322
|
+
procfile = YAML.load_file("Procfile")
|
323
|
+
rescue => e
|
324
|
+
raise ConfigurationError, "Can't read Procfile: #{e.inspect}"
|
264
325
|
end
|
265
326
|
end
|
266
327
|
|
267
|
-
if
|
268
|
-
|
269
|
-
|
328
|
+
if procsd["formation"]
|
329
|
+
formation = procsd["formation"].split(",").map { |f| f.split("=") }.to_h
|
330
|
+
formation.each { |k, v| formation[k] = v.to_i }
|
270
331
|
else
|
271
|
-
|
332
|
+
formation = {}
|
333
|
+
end
|
334
|
+
|
335
|
+
processes = procsd["processes"] || procfile
|
336
|
+
processes.each do |process_name, opts|
|
337
|
+
if opts.kind_of?(Hash)
|
338
|
+
raise ConfigurationError, "Missing start command for `#{process_name}` process" unless opts["start"]
|
339
|
+
else
|
340
|
+
processes[process_name] = { "start" => opts }
|
341
|
+
end
|
342
|
+
|
343
|
+
unless processes[process_name]["count"]
|
344
|
+
processes[process_name]["count"] = formation[process_name] || 1
|
345
|
+
end
|
272
346
|
end
|
273
347
|
|
274
|
-
@
|
275
|
-
@procsd["
|
348
|
+
@config[:processes] = processes
|
349
|
+
@config[:environment] = procsd["environment"] || {}
|
350
|
+
@config[:systemd_dir] = procsd["systemd_dir"] || DEFAULT_SYSTEMD_DIR
|
276
351
|
end
|
277
352
|
end
|
278
353
|
end
|
data/lib/procsd/generator.rb
CHANGED
@@ -6,12 +6,12 @@ module Procsd
|
|
6
6
|
File.dirname(__FILE__)
|
7
7
|
end
|
8
8
|
|
9
|
-
def export!(services,
|
9
|
+
def export!(services, config:, options:)
|
10
10
|
self.destination_root = "/tmp"
|
11
|
-
@
|
12
|
-
say "Systemd directory: #{@
|
11
|
+
@config = config
|
12
|
+
say "Systemd directory: #{@config[:systemd_dir]}"
|
13
13
|
|
14
|
-
app_name = @
|
14
|
+
app_name = @config[:app]
|
15
15
|
target_name = "#{app_name}.target"
|
16
16
|
|
17
17
|
services.each do |service_name, service_command|
|
@@ -19,8 +19,9 @@ module Procsd
|
|
19
19
|
"target_name" => target_name,
|
20
20
|
"id" => service_name.sub(".service", ""),
|
21
21
|
"command" => service_command,
|
22
|
-
"environment" => @
|
22
|
+
"environment" => @config[:environment]
|
23
23
|
)
|
24
|
+
|
24
25
|
generate(service_name, service_config, type: :service)
|
25
26
|
end
|
26
27
|
|
@@ -33,12 +34,14 @@ module Procsd
|
|
33
34
|
|
34
35
|
private
|
35
36
|
|
36
|
-
def generate(filename,
|
37
|
-
template("templates/#{type}.erb", filename,
|
37
|
+
def generate(filename, conf, type:)
|
38
|
+
template("templates/#{type}.erb", filename, conf)
|
38
39
|
|
39
40
|
source_path = File.join(destination_root, filename)
|
40
|
-
dest_path = File.join(@
|
41
|
+
dest_path = File.join(@config[:systemd_dir], filename)
|
41
42
|
system "sudo", "mv", source_path, dest_path
|
43
|
+
ensure
|
44
|
+
File.delete(source_path) if File.exist? source_path
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -26,6 +26,6 @@ StandardInput=null
|
|
26
26
|
SyslogIdentifier=<%= config["id"] %>
|
27
27
|
|
28
28
|
Environment="PATH=<%= config["path"] %>"
|
29
|
-
<% config["environment"].each do |
|
30
|
-
Environment="<%=
|
29
|
+
<% config["environment"].each do |key, value| -%>
|
30
|
+
Environment="<%= key %>=<%= value %>"
|
31
31
|
<% end -%>
|
data/lib/procsd/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: procsd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Afanasev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|