whenever_systemd 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.travis.yml +21 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +151 -0
- data/Rakefile +10 -0
- data/bin/whenever_systemd +67 -0
- data/bin/wheneverize +71 -0
- data/gemfiles/activesupport5.2.gemfile +7 -0
- data/lib/whenever_systemd/command_line.rb +107 -0
- data/lib/whenever_systemd/formatters.rb +90 -0
- data/lib/whenever_systemd/job.rb +118 -0
- data/lib/whenever_systemd/job_list.rb +243 -0
- data/lib/whenever_systemd/os.rb +7 -0
- data/lib/whenever_systemd/output_redirection.rb +57 -0
- data/lib/whenever_systemd/setup.rb +30 -0
- data/lib/whenever_systemd/version.rb +3 -0
- data/lib/whenever_systemd.rb +31 -0
- data/whenever_systemd.gemspec +23 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7074b14d9e4bafc762973076ad3a224ab09243736b19d0742658bbb29c78c142
|
4
|
+
data.tar.gz: b1ddc106d768eed86e517ea59a011cb8b188f6420e5e4f55a1052b31618d3e44
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ba3be14419e7c72cc0f151a577105173072de22bb11b4053e896cfbb897164739d597606b3b7cf1734f07877a3de8968aba4179f12490082753e17189307e08
|
7
|
+
data.tar.gz: e61467600d7cb1d954c1efdfc71749a8c514b7e52b18cf35cc75573bf10a037e96dda5a6a69f6999381b6ac6b1d9c618f2a460fbf431f03f8d39f0f597a3cff7
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
before_install:
|
4
|
+
- gem install bundler
|
5
|
+
- unset _JAVA_OPTIONS
|
6
|
+
rvm:
|
7
|
+
- 2.4.6
|
8
|
+
- 2.5.5
|
9
|
+
- 2.6.3
|
10
|
+
- jruby-9.2.6.0
|
11
|
+
|
12
|
+
gemfile:
|
13
|
+
- gemfiles/activesupport4.1.gemfile
|
14
|
+
- gemfiles/activesupport4.2.gemfile
|
15
|
+
- gemfiles/activesupport5.0.gemfile
|
16
|
+
- gemfiles/activesupport5.1.gemfile
|
17
|
+
- gemfiles/activesupport5.2.gemfile
|
18
|
+
|
19
|
+
env:
|
20
|
+
global:
|
21
|
+
- JRUBY_OPTS=--debug
|
data/Appraisals
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
appraise 'activesupport4.1' do
|
2
|
+
gem "activesupport", "~> 4.1.0"
|
3
|
+
end
|
4
|
+
|
5
|
+
appraise 'activesupport4.2' do
|
6
|
+
gem "activesupport", "~> 4.2.0"
|
7
|
+
end
|
8
|
+
|
9
|
+
appraise 'activesupport5.0' do
|
10
|
+
gem "activesupport", "~> 5.0.0"
|
11
|
+
end
|
12
|
+
|
13
|
+
appraise 'activesupport5.1' do
|
14
|
+
gem "activesupport", "~> 5.1.0"
|
15
|
+
end
|
16
|
+
|
17
|
+
appraise 'activesupport5.2' do
|
18
|
+
gem "activesupport", "~> 5.2.0beta2"
|
19
|
+
end
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Javan Makhmali
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
WheneverSystemd is a fork of the gem [Whenever](https://github.com/javan/whenever), which generates & installs
|
3
|
+
[systemd timers](https://www.freedesktop.org/software/systemd/man/systemd.timer.html#) from a similar `schedule.rb` file.
|
4
|
+
|
5
|
+
Note: By some reasons there is no tests yet, if you want to add them, you are welcome.
|
6
|
+
|
7
|
+
### Installation
|
8
|
+
|
9
|
+
```sh
|
10
|
+
$ gem install whenever_systemd
|
11
|
+
```
|
12
|
+
|
13
|
+
Or with Bundler in your Gemfile.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'whenever_systemd', require: false
|
17
|
+
```
|
18
|
+
|
19
|
+
### Getting started
|
20
|
+
|
21
|
+
```sh
|
22
|
+
$ cd /apps/my-great-project
|
23
|
+
$ bundle exec wheneverize .
|
24
|
+
```
|
25
|
+
|
26
|
+
This will create an initial `config/schedule.rb` file for you (as long as the config folder is already present in your project).
|
27
|
+
|
28
|
+
### The `whenever_systemd` command
|
29
|
+
|
30
|
+
The `whenever_systemd` command will simply show you your `schedule.rb` file converted to cron syntax. It does not read or write your systemd units.
|
31
|
+
|
32
|
+
```sh
|
33
|
+
$ cd /apps/my-great-project
|
34
|
+
$ bundle exec whenever_systemd
|
35
|
+
```
|
36
|
+
|
37
|
+
To write unit files for your jobs, execute this command:
|
38
|
+
|
39
|
+
```sh
|
40
|
+
$ whenever_systemd --update-units
|
41
|
+
```
|
42
|
+
|
43
|
+
Other commonly used options include:
|
44
|
+
```sh
|
45
|
+
$ whenever_systemd --load-file config/my_schedule.rb # set the schedule file
|
46
|
+
$ whenever_systemd --install-path '/usr/lib/systemd/system/' # install units to specific dir
|
47
|
+
```
|
48
|
+
|
49
|
+
### Example schedule.rb file
|
50
|
+
|
51
|
+
**Note the difference with whenever schedule.rb:**
|
52
|
+
|
53
|
+
You should provide a name to your job in the first argument, i.e.:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# instead of:
|
57
|
+
runner "MyModel.some_process"
|
58
|
+
|
59
|
+
# With whenever_systemd:
|
60
|
+
runner "mymodel-some_process", "MyModel.some_process"
|
61
|
+
```
|
62
|
+
|
63
|
+
So, here is an example:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
set :prefix, "myproject"
|
67
|
+
set :timer, { accuracy_sec: "1m" } # timer options
|
68
|
+
set :install, { wanted_by: "timers.target" } # project timers target
|
69
|
+
|
70
|
+
every 3.hours do # 1.minute 1.day 1.week 1.month 1.year is also supported
|
71
|
+
runner "mymodel-some_process", "MyModel.some_process"
|
72
|
+
rake "myrake-task", "my:rake:task"
|
73
|
+
command "my_great_command", "/usr/bin/my_great_command"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Helpers: minutely, hourly, daily, monthly, yearly, quarterly, semiannually
|
77
|
+
# See: https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events
|
78
|
+
minutely do
|
79
|
+
runner "SomeModel.ladeeda"
|
80
|
+
end
|
81
|
+
|
82
|
+
# +every+ helper eats any calendar syntax described in the link above:
|
83
|
+
every '*:1/15' do # Every 15 minutes, starting from 01, i.e.: 01,16,31,46
|
84
|
+
runner "mymode-task_to_run_in_15m", "Mymodel.task_to_run_in_15m"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Folded blocks:
|
88
|
+
daily do
|
89
|
+
at '00:00' do # run every day at 00:00
|
90
|
+
runner "task-do_something_great", "Task.do_something_great"
|
91
|
+
rake "app_server-task", "app_server:task"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
weekly 'Sun' do
|
96
|
+
at '4:30' do # Run every Sunday at 04:30
|
97
|
+
runner "mymodel-sunday_task", "Mymodel.sunday_task"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
### Define your own job types
|
103
|
+
|
104
|
+
Whenever ships with three pre-defined job types: command, runner, and rake. You can define your own with `job_type`.
|
105
|
+
|
106
|
+
For example:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
job_type :awesome, '/usr/local/bin/awesome :task :fun_level'
|
110
|
+
|
111
|
+
every 2.hours do
|
112
|
+
awesome "awesome-party", "party", fun_level: "extreme"
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
Would run `/usr/local/bin/awesome party extreme` every two hours. `:task` is always replaced with the first argument, and any additional `:whatevers` are replaced with the options passed in or by variables that have been defined with `set`.
|
117
|
+
|
118
|
+
The default job types that ship with Whenever are defined like so:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
job_type :command, ":task :output"
|
122
|
+
job_type :rake, "cd :path && :environment_variable=:environment bundle exec rake :task --silent :output"
|
123
|
+
job_type :runner, "cd :path && bin/rails runner -e :environment ':task' :output"
|
124
|
+
job_type :script, "cd :path && :environment_variable=:environment bundle exec script/:task :output"
|
125
|
+
```
|
126
|
+
|
127
|
+
If a `:path` is not set it will default to the directory in which `whenever` was executed. `:environment_variable` will default to 'RAILS_ENV'. `:environment` will default to 'production'. `:output` will be replaced with your output redirection settings which you can read more about here: <http://github.com/javan/whenever/wiki/Output-redirection-aka-logging-your-cron-jobs>
|
128
|
+
|
129
|
+
All jobs are by default run with `bash -l -c 'command...'`. Among other things, this allows your cron jobs to play nice with RVM by loading the entire environment instead of cron's somewhat limited environment. Read more: <http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production>
|
130
|
+
|
131
|
+
You can change this by setting your own `:job_template`.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
set :job_template, "bash -l -c ':job'"
|
135
|
+
```
|
136
|
+
|
137
|
+
Or set the job_template to nil to have your jobs execute normally.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
set :job_template, nil
|
141
|
+
```
|
142
|
+
|
143
|
+
### Credit
|
144
|
+
|
145
|
+
WheneverSystemd is forked from Whenever not by a glory seeker, so I just copy the original credits:
|
146
|
+
|
147
|
+
Whenever was created for use at Inkling (<http://inklingmarkets.com>). Their take on it: <http://blog.inklingmarkets.com/2009/02/whenever-easy-way-to-do-cron-jobs-from.html>
|
148
|
+
|
149
|
+
Thanks to all the contributors who have made it even better: <http://github.com/javan/whenever/contributors>
|
150
|
+
|
151
|
+
Copyright © 2017 Javan Makhmali
|
data/Rakefile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'whenever_systemd'
|
5
|
+
require 'whenever_systemd/version'
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: whenever [options]"
|
11
|
+
|
12
|
+
opts.on('-p', '--prefix [unit prefix]', 'Unit prefix') do |prefix|
|
13
|
+
options[:prefix] = prefix
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on('-d', '--dir [install path]',
|
17
|
+
'Unit Search Path',
|
18
|
+
" Default: #{WheneverSystemd::DEFAULT_INSTALL_PATH}") do |path|
|
19
|
+
options[:install_path] = path
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-i', '--update-units [schedule]',
|
23
|
+
'Install the schedule units.',
|
24
|
+
' Default: full path to schedule.rb file') do |identifier|
|
25
|
+
options[:update] = true
|
26
|
+
options[:identifier] = identifier if identifier
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-c', '--clear-units [schedule]',
|
30
|
+
'Uninstall units',
|
31
|
+
' Default: full path to schedule.rb file') do |identifier|
|
32
|
+
options[:clear] = true
|
33
|
+
options[:identifier] = identifier if identifier
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('-s', '--set [variables]', 'Example: --set \'environment=staging&path=/my/sweet/path\'') do |set|
|
37
|
+
options[:set] = set if set
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-f', '--load-file [schedule file]', 'Default: config/schedule.rb') do |file|
|
41
|
+
options[:file] = file if file
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-u', '--user [user]', 'Default: current user') do |user|
|
45
|
+
options[:user] = user if user
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on('-r', '--roles [role1,role2]', 'Comma-separated list of server roles to generate cron jobs for') do |roles|
|
49
|
+
options[:roles] = roles.split(',').map(&:to_sym) if roles
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on('-S', '--dry-run', 'Only print script which will be executed') do |c|
|
53
|
+
options[:dry] = true
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on("--sudo", "Use sudo with operations") do
|
57
|
+
options[:sudo] = true
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("--script [script]", "Use custom script to perform action") do |v|
|
61
|
+
options[:custom_script] = File.expand_path(v)
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on('-v', '--version') { puts "WheneverSystemd v#{WheneverSystemd::VERSION}"; exit(0) }
|
65
|
+
end.parse!
|
66
|
+
|
67
|
+
WheneverSystemd::CommandLine.execute(options)
|
data/bin/wheneverize
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This file is based heavily on Capistrano's `capify` command
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: #{File.basename($0)} [path]"
|
10
|
+
|
11
|
+
begin
|
12
|
+
opts.parse!(ARGV)
|
13
|
+
rescue OptionParser::ParseError => e
|
14
|
+
warn e.message
|
15
|
+
puts opts
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
unless ARGV.empty?
|
21
|
+
if !File.exist?(ARGV.first)
|
22
|
+
abort "`#{ARGV.first}' does not exist."
|
23
|
+
elsif !File.directory?(ARGV.first)
|
24
|
+
abort "`#{ARGV.first}' is not a directory."
|
25
|
+
elsif ARGV.length > 1
|
26
|
+
abort "Too many arguments; please specify only the directory to wheneverize."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
content = <<-FILE
|
31
|
+
# Use this file to easily define all of your cron jobs.
|
32
|
+
#
|
33
|
+
# It's helpful, but not entirely necessary to understand cron before proceeding.
|
34
|
+
# http://en.wikipedia.org/wiki/Cron
|
35
|
+
|
36
|
+
# Example:
|
37
|
+
#
|
38
|
+
# set :output, "/path/to/my/cron_log.log"
|
39
|
+
#
|
40
|
+
# every 2.hours do
|
41
|
+
# command "/usr/bin/some_great_command"
|
42
|
+
# runner "MyModel.some_method"
|
43
|
+
# rake "some:great:rake:task"
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# every 4.days do
|
47
|
+
# runner "AnotherModel.prune_old_records"
|
48
|
+
# end
|
49
|
+
|
50
|
+
# Learn more: http://github.com/javan/whenever
|
51
|
+
FILE
|
52
|
+
|
53
|
+
file = 'config/schedule.rb'
|
54
|
+
base = ARGV.empty? ? '.' : ARGV.shift
|
55
|
+
|
56
|
+
file = File.join(base, file)
|
57
|
+
if File.exist?(file)
|
58
|
+
warn "[skip] `#{file}' already exists"
|
59
|
+
elsif File.exist?(file.downcase)
|
60
|
+
warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'"
|
61
|
+
else
|
62
|
+
dir = File.dirname(file)
|
63
|
+
if !File.exist?(dir)
|
64
|
+
warn "[add] creating `#{dir}'"
|
65
|
+
FileUtils.mkdir_p(dir)
|
66
|
+
end
|
67
|
+
puts "[add] writing `#{file}'"
|
68
|
+
File.open(file, "w") { |f| f.write(content) }
|
69
|
+
end
|
70
|
+
|
71
|
+
puts "[done] wheneverized!"
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module WheneverSystemd
|
7
|
+
class CommandLine
|
8
|
+
def self.execute(options={})
|
9
|
+
new(options).run
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
@options = options
|
14
|
+
|
15
|
+
@options[:install_path] ||= WheneverSystemd::DEFAULT_INSTALL_PATH
|
16
|
+
@options[:temp_path] ||= "#{ENV['HOME']}/tmp/whenever-#{Time.now.to_i}"
|
17
|
+
@options[:file] ||= "config/schedule.rb"
|
18
|
+
@options[:cut] ||= 0
|
19
|
+
@options[:identifier] ||= default_identifier
|
20
|
+
|
21
|
+
if !File.exist?(@options[:file]) && @options[:clear].nil?
|
22
|
+
warn("[fail] Can't find file: #{@options[:file]}")
|
23
|
+
exit(1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
if @options[:dry]
|
29
|
+
dry_run
|
30
|
+
elsif @options[:clear]
|
31
|
+
clear_units
|
32
|
+
elsif @options[:update]
|
33
|
+
update_units
|
34
|
+
else
|
35
|
+
show_units
|
36
|
+
exit(0)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def show_units
|
41
|
+
puts job_list.dry_units(@options[:install_path])
|
42
|
+
puts "## [message] Above is your schedule file converted to systemd units."
|
43
|
+
puts "## [message] Your active units was not updated."
|
44
|
+
puts "## [message] Run `whenever --help' for more options."
|
45
|
+
end
|
46
|
+
|
47
|
+
def dry_run
|
48
|
+
if @options[:clear]
|
49
|
+
puts job_list.generate_clear_script(@options[:install_path])
|
50
|
+
elsif @options[:update]
|
51
|
+
puts job_list.generate_update_script(@options[:install_path])
|
52
|
+
else
|
53
|
+
show_units
|
54
|
+
end
|
55
|
+
exit(0)
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_units
|
59
|
+
make_and_run_script(:update_units)
|
60
|
+
end
|
61
|
+
|
62
|
+
def clear_units
|
63
|
+
make_and_run_script(:clear_units)
|
64
|
+
end
|
65
|
+
|
66
|
+
def job_list
|
67
|
+
@job_list ||= JobList.new(@options)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def make_and_run_script(name)
|
73
|
+
script_name = [@options[:prefix], name].compact.join(?-)
|
74
|
+
|
75
|
+
script_path = make_script(script_name) do
|
76
|
+
job_list.generate_update_script(@options[:install_path])
|
77
|
+
end
|
78
|
+
|
79
|
+
cmd =
|
80
|
+
if @options[:custom_script]
|
81
|
+
binding.eval(Pathname(@options[:custom_script]).read)
|
82
|
+
else
|
83
|
+
sudo_if_need(script_path)
|
84
|
+
end
|
85
|
+
|
86
|
+
system(cmd)
|
87
|
+
end
|
88
|
+
|
89
|
+
def sudo_if_need(*cmd)
|
90
|
+
Shellwords.join(@options[:sudo] ? ["sudo", "bash", *cmd] : ["bash", *cmd])
|
91
|
+
end
|
92
|
+
|
93
|
+
def make_script(name)
|
94
|
+
FileUtils.mkdir_p(@options[:temp_path])
|
95
|
+
script_file = Pathname(@options[:temp_path])/"#{name}.sh"
|
96
|
+
script_file.write(yield)
|
97
|
+
# script_file.chmod(0755)
|
98
|
+
script_file.to_path
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def default_identifier
|
104
|
+
File.expand_path(@options[:file])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WheneverSystemd
|
4
|
+
module Formatters
|
5
|
+
factory = proc { |tpl, args| tpl % args }.curry(2).freeze
|
6
|
+
|
7
|
+
ParamPair = factory["%s=%s"].freeze
|
8
|
+
|
9
|
+
capitalize = -> (part) do
|
10
|
+
part.match?(/\A[a-z][a-z0-9]+\z/) ? part.capitalize : part
|
11
|
+
end
|
12
|
+
|
13
|
+
ParamKey = -> (key) do
|
14
|
+
key.to_s.split('_').map(&capitalize).join
|
15
|
+
end
|
16
|
+
|
17
|
+
hash_join_params = -> (hash) do
|
18
|
+
hash.transform_keys(&ParamKey).map(&ParamPair) * "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
ParamsHash = -> (hash) do
|
22
|
+
hash.transform_values(&hash_join_params)
|
23
|
+
end
|
24
|
+
|
25
|
+
IntervalMinutes = factory["*:0/%{minutes}"]
|
26
|
+
IntervalHours = factory["0/%{hours}:0/%{minutes}"] >> proc { |v| v.chomp("/0") }
|
27
|
+
IntervalSeconds = factory["0:0:0/%d"]
|
28
|
+
|
29
|
+
NormalizeInterval = {
|
30
|
+
"daily" => "*-*-*"
|
31
|
+
}
|
32
|
+
|
33
|
+
Duration = -> (freq) do
|
34
|
+
case freq
|
35
|
+
when 0...3600; IntervalMinutes[freq.parts] # 1 second to hour
|
36
|
+
when 3600...86400; IntervalHours[freq.parts] # 1 hour to day
|
37
|
+
else IntervalSeconds[freq.to_i]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Freq = -> (freq) do
|
42
|
+
ActiveSupport::Duration === freq ? Duration[freq] : freq
|
43
|
+
end
|
44
|
+
|
45
|
+
Service = ParamsHash >> factory[<<~INI]
|
46
|
+
[Unit]
|
47
|
+
%{unit}
|
48
|
+
|
49
|
+
[Service]
|
50
|
+
%{service}
|
51
|
+
INI
|
52
|
+
|
53
|
+
Timer = ParamsHash >> factory[<<~INI]
|
54
|
+
[Unit]
|
55
|
+
%{unit}
|
56
|
+
|
57
|
+
[Timer]
|
58
|
+
%{timer}
|
59
|
+
|
60
|
+
[Install]
|
61
|
+
%{install}
|
62
|
+
INI
|
63
|
+
|
64
|
+
Target = ParamsHash >> factory[<<~INI]
|
65
|
+
[Unit]
|
66
|
+
%{unit}
|
67
|
+
|
68
|
+
[Install]
|
69
|
+
WantedBy=timers.target
|
70
|
+
INI
|
71
|
+
|
72
|
+
MaterializeUnit = factory[<<~BASH]
|
73
|
+
cat > %{path}/%{filename} <<'EOF'
|
74
|
+
%{content}
|
75
|
+
EOF
|
76
|
+
BASH
|
77
|
+
|
78
|
+
MaterializeUnits = proc do |list|
|
79
|
+
list.map(&MaterializeUnit) * "\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
DryUnit = factory[<<~EOF]
|
83
|
+
# filepath: %{path}/%{filename}
|
84
|
+
%{content}
|
85
|
+
|
86
|
+
EOF
|
87
|
+
|
88
|
+
DryUnits = proc { |list| list.map(&DryUnit) * "\n" }
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require "whenever_systemd/formatters"
|
3
|
+
|
4
|
+
module WheneverSystemd
|
5
|
+
class Job
|
6
|
+
attr_reader :at, :roles, :mailto, :name
|
7
|
+
|
8
|
+
def initialize(name, options = {})
|
9
|
+
@name = name
|
10
|
+
@options = options
|
11
|
+
@at = options.delete(:at)
|
12
|
+
@template = options.delete(:template)
|
13
|
+
@mailto = options.fetch(:mailto, :default_mailto)
|
14
|
+
@job_template = options.delete(:job_template) || ":job"
|
15
|
+
@roles = Array(options.delete(:roles))
|
16
|
+
@options[:output] = options.has_key?(:output) ? Output::Redirection.new(options[:output]).to_s : ''
|
17
|
+
@options[:environment_variable] ||= "RAILS_ENV"
|
18
|
+
@options[:environment] ||= :production
|
19
|
+
@options[:path] = Shellwords.shellescape(@options[:path] || WheneverSystemd.path)
|
20
|
+
|
21
|
+
description = options.delete(:description)
|
22
|
+
install = options.delete(:install) { { wanted_by: "timers.target" } }
|
23
|
+
|
24
|
+
timer = options.delete(:timer).to_h
|
25
|
+
timer[:on_calendar] ||= compose_on_calendar(options.delete(:interval), @at)
|
26
|
+
|
27
|
+
unit = options.delete(:unit).to_h
|
28
|
+
unit[:description] = description
|
29
|
+
|
30
|
+
service = options.delete(:service).to_h
|
31
|
+
service[:type] ||= "oneshot"
|
32
|
+
service[:exec_start] = output
|
33
|
+
|
34
|
+
@service_options = { unit: unit, service: service }
|
35
|
+
@timer_options = { unit: { description: description }, timer: timer, install: install }
|
36
|
+
end
|
37
|
+
|
38
|
+
def compose_on_calendar(*args)
|
39
|
+
args.compact!
|
40
|
+
if args.size > 1 && Formatters::NormalizeInterval.key?(args[0])
|
41
|
+
args[0] = Formatters::NormalizeInterval[args[0]]
|
42
|
+
end
|
43
|
+
args.join(" ")
|
44
|
+
end
|
45
|
+
|
46
|
+
def output
|
47
|
+
job = process_template(@template, @options)
|
48
|
+
out = process_template(@job_template, @options.merge(:job => job))
|
49
|
+
out.gsub(/%/, '\%')
|
50
|
+
end
|
51
|
+
|
52
|
+
def unprefixed_name(prefix = nil)
|
53
|
+
prefix ||= @options[:prefix]
|
54
|
+
if @name.rindex(prefix) == 0
|
55
|
+
prefix_end = prefix.size.next
|
56
|
+
@name[prefix_end, @name.size - prefix_end]
|
57
|
+
else
|
58
|
+
@name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def service_name
|
63
|
+
"#{@name}.service"
|
64
|
+
end
|
65
|
+
|
66
|
+
def timer_name
|
67
|
+
"#{@name}.timer"
|
68
|
+
end
|
69
|
+
|
70
|
+
def unit_expansion
|
71
|
+
"#{@name}.{service,timer}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def systemd_units(path)
|
75
|
+
[
|
76
|
+
{ path: path, filename: service_name, content: systemd_service },
|
77
|
+
{ path: path, filename: timer_name, content: systemd_timer }
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
def systemd_service
|
82
|
+
Formatters::Service[@service_options]
|
83
|
+
end
|
84
|
+
|
85
|
+
def systemd_timer
|
86
|
+
Formatters::Timer[@timer_options]
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_role?(role)
|
90
|
+
roles.empty? || roles.include?(role)
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def process_template(template, options)
|
96
|
+
template.gsub(/:\w+/) do |key|
|
97
|
+
before_and_after = [$`[-1..-1], $'[0..0]]
|
98
|
+
option = options[key.sub(':', '').to_sym] || key
|
99
|
+
|
100
|
+
if before_and_after.all? { |c| c == "'" }
|
101
|
+
escape_single_quotes(option)
|
102
|
+
elsif before_and_after.all? { |c| c == '"' }
|
103
|
+
escape_double_quotes(option)
|
104
|
+
else
|
105
|
+
option
|
106
|
+
end
|
107
|
+
end.gsub(/\s+/m, " ").strip
|
108
|
+
end
|
109
|
+
|
110
|
+
def escape_single_quotes(str)
|
111
|
+
str.gsub(/'/) { "'\\''" }
|
112
|
+
end
|
113
|
+
|
114
|
+
def escape_double_quotes(str)
|
115
|
+
str.gsub(/"/) { '\"' }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "whenever_systemd/formatters"
|
4
|
+
require "active_support/duration"
|
5
|
+
require "active_support/core_ext/numeric/time"
|
6
|
+
require "active_support/core_ext/object/deep_dup"
|
7
|
+
|
8
|
+
module WheneverSystemd
|
9
|
+
class JobList
|
10
|
+
attr_reader :roles
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@jobs, @env, @set_variables, @pre_set_variables = [], {}, {}, {}
|
14
|
+
|
15
|
+
if options.is_a? String
|
16
|
+
options = { :string => options }
|
17
|
+
end
|
18
|
+
@temp_path = options[:temp_path]
|
19
|
+
pre_set(options[:set])
|
20
|
+
@roles = options[:roles] || []
|
21
|
+
|
22
|
+
setup_file = File.expand_path('../setup.rb', __FILE__)
|
23
|
+
setup = File.read(setup_file)
|
24
|
+
schedule = if options[:string]
|
25
|
+
options[:string]
|
26
|
+
elsif options[:file]
|
27
|
+
File.read(options[:file])
|
28
|
+
end
|
29
|
+
|
30
|
+
instance_eval(setup, setup_file)
|
31
|
+
instance_eval(schedule, options[:file] || '<eval>')
|
32
|
+
end
|
33
|
+
|
34
|
+
def set(variable, value)
|
35
|
+
variable = variable.to_sym
|
36
|
+
return if @pre_set_variables[variable]
|
37
|
+
|
38
|
+
instance_variable_set("@#{variable}".to_sym, value)
|
39
|
+
@set_variables[variable] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def method_missing(name, *args, &block)
|
43
|
+
@set_variables.has_key?(name) ? @set_variables[name] : super
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.respond_to?(name, include_private = false)
|
47
|
+
@set_variables.has_key?(name) || super
|
48
|
+
end
|
49
|
+
|
50
|
+
def env(variable, value)
|
51
|
+
@env[variable.to_s] = value
|
52
|
+
end
|
53
|
+
|
54
|
+
def every(frequency, at: nil, **timer)
|
55
|
+
@options_was = @options
|
56
|
+
@options = @options.to_h.merge(interval: Formatters::Freq[frequency], timer: timer, at: at)
|
57
|
+
yield
|
58
|
+
ensure
|
59
|
+
@options, @options_was = @options_was, nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def daily(at: "00:00:00", **options, &block)
|
63
|
+
every("*-*-*", at: at, **options, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
%w(minutely hourly monthly yearly quarterly semiannually).each do |k|
|
67
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
68
|
+
def #{k}(**options, &block)
|
69
|
+
every(:#{k}, **options, &block)
|
70
|
+
end
|
71
|
+
RUBY
|
72
|
+
end
|
73
|
+
|
74
|
+
def weekly(*on_days, **options, &block)
|
75
|
+
if on_days.size > 0
|
76
|
+
every("#{on_days * ?,} *-*-*", **options, &block)
|
77
|
+
else
|
78
|
+
every(:weekly, **options, &block)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def at(time)
|
83
|
+
@options_was, @options = @options, @options.to_h.merge(at: time)
|
84
|
+
yield
|
85
|
+
ensure
|
86
|
+
@options, @options_was = @options_was, nil
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :jobs
|
90
|
+
|
91
|
+
def job_type(name, template)
|
92
|
+
singleton_class.class_eval do
|
93
|
+
define_method(name) do |job_name, task, *args|
|
94
|
+
options = { :task => task, :template => template }
|
95
|
+
options.merge!(args[0]) if args[0].is_a? Hash
|
96
|
+
options[:description] ||= options.delete("?") { "WheneverSystemd-generated Job" }
|
97
|
+
|
98
|
+
@jobs ||= []
|
99
|
+
@jobs << Job.new("#{@prefix}-#{job_name}", @set_variables.deep_merge(@options).deep_merge(options).deep_dup)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate(name, path)
|
105
|
+
case name.to_sym
|
106
|
+
when :units; generate_units_script(path)
|
107
|
+
when :update_units; generate_update_script(path)
|
108
|
+
when :clear_units; generate_clear_script(path)
|
109
|
+
else raise ArgumentError, %(available names: units, update_units, clear_units, given: #{name})
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def generate_units_script(path)
|
114
|
+
Formatters::MaterializeUnits[systemd_units(path)]
|
115
|
+
end
|
116
|
+
|
117
|
+
def generate_update_script(path)
|
118
|
+
[
|
119
|
+
make_backup_dir,
|
120
|
+
backup_previous_units_from(path, all: true),
|
121
|
+
systemctl_timers('disable', '--now', all: true),
|
122
|
+
generate_units_script(@temp_path),
|
123
|
+
copy_updated_units_to(path),
|
124
|
+
Shellwords.join(["/usr/bin/systemctl", "daemon-reload"]),
|
125
|
+
systemctl_timers('enable', '--now')
|
126
|
+
].join("\n\n")
|
127
|
+
end
|
128
|
+
|
129
|
+
def generate_clear_script(path)
|
130
|
+
[
|
131
|
+
make_backup_dir,
|
132
|
+
backup_previous_units_from(path, all: true),
|
133
|
+
systemctl_timers('disable', '--now', all: true),
|
134
|
+
format(%(/usr/bin/rm -rfI %{target}/%{expansion}), target: Shellwords.escape(path), expansion: units_expansion(all: true))
|
135
|
+
].join("\n\n")
|
136
|
+
end
|
137
|
+
|
138
|
+
def backup_previous_units_from(path, **opts)
|
139
|
+
format(%(/usr/bin/cp -rvf %{source}/%{expansion} -t %{target}),
|
140
|
+
source: Shellwords.escape(path),
|
141
|
+
expansion: units_expansion(**opts),
|
142
|
+
target: Shellwords.escape("#{@temp_path}/backup")
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
def copy_updated_units_to(path)
|
147
|
+
format(%(/usr/bin/cp -rvf %{source}/*.{service,timer} -t %{target}),
|
148
|
+
source: Shellwords.escape(@temp_path),
|
149
|
+
target: Shellwords.escape(path)
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
def make_backup_dir
|
154
|
+
Shellwords.join(["mkdir", "-p", "#{@temp_path}/backup"])
|
155
|
+
end
|
156
|
+
|
157
|
+
def systemctl_timers(*args, **opts)
|
158
|
+
case args[0]
|
159
|
+
when "enable", "disable"
|
160
|
+
format(%(for timer in %s; do %s $timer; done),
|
161
|
+
units_expansion('timer', sub: true, **opts),
|
162
|
+
Shellwords.join(["/usr/bin/systemctl", *args])
|
163
|
+
)
|
164
|
+
else
|
165
|
+
Shellwords.join(["/usr/bin/systemctl", *args, units_expansion('timer', **opts)])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def timers
|
170
|
+
@jobs.map(&:timer_name)
|
171
|
+
end
|
172
|
+
|
173
|
+
def unit_files(path)
|
174
|
+
Dir.glob("#{path}/#{units_expansion}")
|
175
|
+
end
|
176
|
+
|
177
|
+
def units_expansion(ext = "{service,timer}", all: false, sub: false)
|
178
|
+
suffixes = all ? "*" : format("{%s}", @jobs.map { |j| Shellwords.escape(j.unprefixed_name) }.join(?,))
|
179
|
+
pattern = "#{@prefix}-#{suffixes}.#{ext}"
|
180
|
+
return pattern unless sub
|
181
|
+
if all
|
182
|
+
format(%($(/usr/bin/systemctl list-unit-files '%s' | /usr/bin/cut -d ' ' -f 1 | /usr/bin/head -n -2 | /usr/bin/tail -n +2)), pattern)
|
183
|
+
else
|
184
|
+
format(%($(/usr/bin/echo %s)), pattern)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def dry_units(path)
|
189
|
+
Formatters::DryUnits[systemd_units(path)]
|
190
|
+
end
|
191
|
+
|
192
|
+
def systemd_units(path, include_target = true)
|
193
|
+
wanted_by = @set_variables.dig(:install, :wanted_by)
|
194
|
+
if include_target && wanted_by != "timers.target"
|
195
|
+
[timers_target(path), *systemd_units("#{path}/#{wanted_by}.wants", false)]
|
196
|
+
else
|
197
|
+
@jobs.flat_map { |job| job.systemd_units(path) }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def timers_target(path)
|
202
|
+
{
|
203
|
+
path: path,
|
204
|
+
filename: @set_variables.dig(:install, :wanted_by),
|
205
|
+
content: Formatters::Target[unit: { description: "Timers target" }]
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
#
|
212
|
+
# Takes a string like: "variable1=something&variable2=somethingelse"
|
213
|
+
# and breaks it into variable/value pairs. Used for setting variables at runtime from the command line.
|
214
|
+
# Only works for setting values as strings.
|
215
|
+
#
|
216
|
+
def pre_set(variable_string = nil)
|
217
|
+
return if variable_string.nil? || variable_string == ""
|
218
|
+
|
219
|
+
pairs = variable_string.split('&')
|
220
|
+
pairs.each do |pair|
|
221
|
+
next unless pair.index('=')
|
222
|
+
variable, value = *pair.split('=')
|
223
|
+
unless variable.nil? || variable == "" || value.nil? || value == ""
|
224
|
+
variable = variable.strip.to_sym
|
225
|
+
set(variable, value.strip)
|
226
|
+
@pre_set_variables[variable] = value
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def environment_variables
|
232
|
+
return if @env.empty?
|
233
|
+
|
234
|
+
output = []
|
235
|
+
@env.each do |key, val|
|
236
|
+
output << "#{key}=#{val.nil? || val == "" ? '""' : val}\n"
|
237
|
+
end
|
238
|
+
output << "\n"
|
239
|
+
|
240
|
+
output.join
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module WheneverSystemd
|
2
|
+
module Output
|
3
|
+
class Redirection
|
4
|
+
def initialize(output)
|
5
|
+
@output = output
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
return '' unless defined?(@output)
|
10
|
+
case @output
|
11
|
+
when String then redirect_from_string
|
12
|
+
when Hash then redirect_from_hash
|
13
|
+
when NilClass then ">> /dev/null 2>&1"
|
14
|
+
when Proc then @output.call
|
15
|
+
else ''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def stdout
|
22
|
+
return unless @output.has_key?(:standard)
|
23
|
+
@output[:standard].nil? ? '/dev/null' : @output[:standard]
|
24
|
+
end
|
25
|
+
|
26
|
+
def stderr
|
27
|
+
return unless @output.has_key?(:error)
|
28
|
+
@output[:error].nil? ? '/dev/null' : @output[:error]
|
29
|
+
end
|
30
|
+
|
31
|
+
def redirect_from_hash
|
32
|
+
case
|
33
|
+
when stdout == '/dev/null' && stderr == '/dev/null'
|
34
|
+
"> /dev/null 2>&1"
|
35
|
+
when stdout && stderr == '/dev/null'
|
36
|
+
">> #{stdout} 2> /dev/null"
|
37
|
+
when stdout && stderr
|
38
|
+
">> #{stdout} 2>> #{stderr}"
|
39
|
+
when stderr == '/dev/null'
|
40
|
+
"2> /dev/null"
|
41
|
+
when stderr
|
42
|
+
"2>> #{stderr}"
|
43
|
+
when stdout == '/dev/null'
|
44
|
+
"> /dev/null"
|
45
|
+
when stdout
|
46
|
+
">> #{stdout}"
|
47
|
+
else
|
48
|
+
''
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def redirect_from_string
|
53
|
+
">> #{@output} 2>&1"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Environment variable defaults to RAILS_ENV
|
2
|
+
set :environment_variable, "RAILS_ENV"
|
3
|
+
# Environment defaults to production
|
4
|
+
set :environment, "production"
|
5
|
+
# Path defaults to the directory `whenever` was run from
|
6
|
+
set :path, WheneverSystemd.path
|
7
|
+
|
8
|
+
# All jobs are wrapped in this template.
|
9
|
+
# http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production
|
10
|
+
set :job_template, "/bin/bash -l -c ':job'"
|
11
|
+
|
12
|
+
set :prefix, "myproject"
|
13
|
+
set :timer, { accuracy_sec: "1m" }
|
14
|
+
set :install, { wanted_by: "timers.target" }
|
15
|
+
|
16
|
+
set :runner_command, case
|
17
|
+
when WheneverSystemd.bin_rails?
|
18
|
+
"bin/rails runner"
|
19
|
+
when WheneverSystemd.script_rails?
|
20
|
+
"script/rails runner"
|
21
|
+
else
|
22
|
+
"script/runner"
|
23
|
+
end
|
24
|
+
|
25
|
+
set :bundle_command, WheneverSystemd.bundler? ? "bundle exec" : ""
|
26
|
+
|
27
|
+
job_type :command, ":task :output"
|
28
|
+
job_type :rake, "cd :path && :environment_variable=:environment :bundle_command rake :task --silent :output"
|
29
|
+
job_type :script, "cd :path && :environment_variable=:environment :bundle_command script/:task :output"
|
30
|
+
job_type :runner, "cd :path && :bundle_command :runner_command -e :environment ':task' :output"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'whenever_systemd/job_list'
|
4
|
+
require 'whenever_systemd/job'
|
5
|
+
require 'whenever_systemd/command_line'
|
6
|
+
require 'whenever_systemd/output_redirection'
|
7
|
+
require 'whenever_systemd/os'
|
8
|
+
|
9
|
+
module WheneverSystemd
|
10
|
+
DEFAULT_INSTALL_PATH = "/etc/systemd/system"
|
11
|
+
|
12
|
+
def self.cron(options)
|
13
|
+
JobList.new(options).dry_units(options[:install_path])
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.path
|
17
|
+
Dir.pwd
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.bin_rails?
|
21
|
+
File.exist?(File.join(path, 'bin', 'rails'))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.script_rails?
|
25
|
+
File.exist?(File.join(path, 'script', 'rails'))
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.bundler?
|
29
|
+
File.exist?(File.join(path, 'Gemfile'))
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "whenever_systemd/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "whenever_systemd"
|
7
|
+
s.version = WheneverSystemd::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Javan Makhmali", "Anton Semenov"]
|
10
|
+
s.email = ["javan@javan.us", "anton.estum@gmail.com"]
|
11
|
+
s.license = "MIT"
|
12
|
+
s.homepage = "https://github.com/estum/whenever"
|
13
|
+
s.summary = %q{Systemd Timers in ruby.}
|
14
|
+
s.description = %q{Clean ruby syntax for writing and deploying systemd timers.}
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
# s.test_files = `git ls-files -- test/{functional,unit}/*`.split("\n")
|
17
|
+
s.executables = ["whenever_systemd", "wheneverize"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.required_ruby_version = ">= 2.6"
|
20
|
+
|
21
|
+
s.add_dependency "activesupport", ">= 5.2"
|
22
|
+
s.add_development_dependency "bundler"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: whenever_systemd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Javan Makhmali
|
8
|
+
- Anton Semenov
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2022-02-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '5.2'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '5.2'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundler
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: Clean ruby syntax for writing and deploying systemd timers.
|
43
|
+
email:
|
44
|
+
- javan@javan.us
|
45
|
+
- anton.estum@gmail.com
|
46
|
+
executables:
|
47
|
+
- whenever_systemd
|
48
|
+
- wheneverize
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- ".gitignore"
|
53
|
+
- ".travis.yml"
|
54
|
+
- Appraisals
|
55
|
+
- CHANGELOG.md
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- bin/whenever_systemd
|
61
|
+
- bin/wheneverize
|
62
|
+
- gemfiles/activesupport5.2.gemfile
|
63
|
+
- lib/whenever_systemd.rb
|
64
|
+
- lib/whenever_systemd/command_line.rb
|
65
|
+
- lib/whenever_systemd/formatters.rb
|
66
|
+
- lib/whenever_systemd/job.rb
|
67
|
+
- lib/whenever_systemd/job_list.rb
|
68
|
+
- lib/whenever_systemd/os.rb
|
69
|
+
- lib/whenever_systemd/output_redirection.rb
|
70
|
+
- lib/whenever_systemd/setup.rb
|
71
|
+
- lib/whenever_systemd/version.rb
|
72
|
+
- whenever_systemd.gemspec
|
73
|
+
homepage: https://github.com/estum/whenever
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.6'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.0.3
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Systemd Timers in ruby.
|
96
|
+
test_files: []
|