whenever_systemd 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +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: []
|