lux-hammer 0.3.15 → 0.3.16
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/.version +1 -1
- data/AGENTS.md +1 -1
- data/README.md +2 -2
- data/lib/hammer/builtins.rb +2 -2
- data/lib/hammer/cron_server.rb +67 -32
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1e64c1f3c63eddb83350353d2a5f5977e5afe544a68d9ed43eea129debc017e7
|
|
4
|
+
data.tar.gz: 8e5cfe7fa2ff99d970f172b77e5d23fb7ea27722f5103cfd2edf2c4aba4d5579
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 452eee357de3639419009dae5b0160ab337e0dbea42aee6c44e084284e5e5557d2c2df8c803ed2bd8564e4808f49e1422c1bb9d5ea9a6bbbab57f1f217f2084a
|
|
7
|
+
data.tar.gz: 6572ee28bdb6e8f26328a315d29bdde5bb0a63190b13fa7255008b3d44db23c3337616fb485b78e3eeab73822d30e1c1263457ee473c00a94dae073e65ded3c6
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.16
|
data/AGENTS.md
CHANGED
|
@@ -313,7 +313,7 @@ Task contracts:
|
|
|
313
313
|
`hammer/cron_web.rb` - no webrick, keep it dependency-free): jobs
|
|
314
314
|
table, per-job log tail, POST run-now, `/json` status. Flags:
|
|
315
315
|
`--port`, `--pass` (HTTP basic auth on every route, any username,
|
|
316
|
-
constant-time compare; falls back to `
|
|
316
|
+
constant-time compare; falls back to `CRON_HTTP_PASS` env),
|
|
317
317
|
`--list` (print jobs + next runs, exit), `--service` (print launchd
|
|
318
318
|
plist / systemd unit, carries `--pass` when set, install hints on
|
|
319
319
|
stderr).
|
data/README.md
CHANGED
|
@@ -683,11 +683,11 @@ On a shared machine, protect the UI with HTTP basic auth - useful since
|
|
|
683
683
|
"run now" executes tasks:
|
|
684
684
|
|
|
685
685
|
```sh
|
|
686
|
-
hammer h:cron --pass=secret # or:
|
|
686
|
+
hammer h:cron --pass=secret # or: CRON_HTTP_PASS=secret hammer h:cron
|
|
687
687
|
curl -u :secret http://127.0.0.1:4267/json # any username, password checked
|
|
688
688
|
```
|
|
689
689
|
|
|
690
|
-
Prefer the `
|
|
690
|
+
Prefer the `CRON_HTTP_PASS` env var when other users can read your
|
|
691
691
|
process list (`ps` shows flag values). A `--service` unit generated
|
|
692
692
|
while `--pass` is set carries the flag along, so keep that file private.
|
|
693
693
|
Note this is plain HTTP - credentials are encoded, not encrypted; for
|
data/lib/hammer/builtins.rb
CHANGED
|
@@ -135,7 +135,7 @@ class Hammer
|
|
|
135
135
|
example 'h:cron --list'
|
|
136
136
|
example 'h:cron --service > ~/.config/systemd/user/hammer-cron.service'
|
|
137
137
|
opt :port, type: :integer, default: 4267, desc: 'web UI port (binds 127.0.0.1 only)'
|
|
138
|
-
opt :pass, type: :string, desc: 'HTTP basic-auth password for the web UI (or
|
|
138
|
+
opt :pass, type: :string, desc: 'HTTP basic-auth password for the web UI (or CRON_HTTP_PASS env)'
|
|
139
139
|
opt :list, type: :boolean, desc: 'print scheduled jobs + next runs, then exit'
|
|
140
140
|
opt :service, type: :boolean, desc: 'print a launchd plist (macOS) / systemd unit (linux)'
|
|
141
141
|
proc do |opts|
|
|
@@ -144,7 +144,7 @@ class Hammer
|
|
|
144
144
|
end
|
|
145
145
|
require_relative 'cron_server'
|
|
146
146
|
server = Hammer::CronServer.new(self.class.root, port: opts[:port],
|
|
147
|
-
pass: opts[:pass] || ENV['
|
|
147
|
+
pass: opts[:pass] || ENV['CRON_HTTP_PASS'])
|
|
148
148
|
if opts[:service]
|
|
149
149
|
server.print_service_unit
|
|
150
150
|
elsif opts[:list]
|
data/lib/hammer/cron_server.rb
CHANGED
|
@@ -265,41 +265,11 @@ class Hammer
|
|
|
265
265
|
def print_service_unit
|
|
266
266
|
if RUBY_PLATFORM.include?('darwin')
|
|
267
267
|
label = "com.lux-hammer.cron.#{File.basename(@root_dir)}"
|
|
268
|
-
puts
|
|
269
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
270
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
271
|
-
<plist version="1.0">
|
|
272
|
-
<dict>
|
|
273
|
-
<key>Label</key><string>#{label}</string>
|
|
274
|
-
<key>ProgramArguments</key>
|
|
275
|
-
<array>
|
|
276
|
-
<string>#{@hammer_bin}</string>
|
|
277
|
-
<string>h:cron</string>
|
|
278
|
-
<string>--port=#{@port}</string>#{@pass ? "\n <string>--pass=#{@pass}</string>" : ''}
|
|
279
|
-
</array>
|
|
280
|
-
<key>WorkingDirectory</key><string>#{@root_dir}</string>
|
|
281
|
-
<key>RunAtLoad</key><true/>
|
|
282
|
-
<key>KeepAlive</key><true/>
|
|
283
|
-
<key>StandardOutPath</key><string>#{@root_dir}/log/hammer/cron-server.log</string>
|
|
284
|
-
<key>StandardErrorPath</key><string>#{@root_dir}/log/hammer/cron-server.log</string>
|
|
285
|
-
</dict>
|
|
286
|
-
</plist>
|
|
287
|
-
XML
|
|
268
|
+
puts launchd_service_unit(label)
|
|
288
269
|
warn Shell.paint("# save to ~/Library/LaunchAgents/#{label}.plist, then:", :gray)
|
|
289
270
|
warn Shell.paint("# launchctl load ~/Library/LaunchAgents/#{label}.plist", :gray)
|
|
290
271
|
else
|
|
291
|
-
puts
|
|
292
|
-
[Unit]
|
|
293
|
-
Description=hammer h:cron job server (#{File.basename(@root_dir)})
|
|
294
|
-
|
|
295
|
-
[Service]
|
|
296
|
-
ExecStart=#{@hammer_bin} h:cron --port=#{@port}#{" --pass=#{@pass}" if @pass}
|
|
297
|
-
WorkingDirectory=#{@root_dir}
|
|
298
|
-
Restart=on-failure
|
|
299
|
-
|
|
300
|
-
[Install]
|
|
301
|
-
WantedBy=default.target
|
|
302
|
-
UNIT
|
|
272
|
+
puts systemd_service_unit
|
|
303
273
|
warn Shell.paint('# save to ~/.config/systemd/user/hammer-cron.service, then:', :gray)
|
|
304
274
|
warn Shell.paint('# systemctl --user enable --now hammer-cron', :gray)
|
|
305
275
|
end
|
|
@@ -326,6 +296,71 @@ class Hammer
|
|
|
326
296
|
|
|
327
297
|
private
|
|
328
298
|
|
|
299
|
+
def launchd_service_unit(label = "com.lux-hammer.cron.#{File.basename(@root_dir)}")
|
|
300
|
+
args = [@hammer_bin, 'h:cron', "--port=#{@port}"]
|
|
301
|
+
args << "--pass=#{@pass}" if @pass
|
|
302
|
+
arguments = args.map { |arg| " <string>#{xml_escape(arg)}</string>" }.join("\n")
|
|
303
|
+
log_path = File.join(@root_dir, 'log/hammer/cron-server.log')
|
|
304
|
+
|
|
305
|
+
<<~XML
|
|
306
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
307
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
308
|
+
<plist version="1.0">
|
|
309
|
+
<dict>
|
|
310
|
+
<key>Label</key><string>#{xml_escape(label)}</string>
|
|
311
|
+
<key>ProgramArguments</key>
|
|
312
|
+
<array>
|
|
313
|
+
#{arguments}
|
|
314
|
+
</array>
|
|
315
|
+
<key>WorkingDirectory</key><string>#{xml_escape(@root_dir)}</string>
|
|
316
|
+
<key>RunAtLoad</key><true/>
|
|
317
|
+
<key>KeepAlive</key><true/>
|
|
318
|
+
<key>StandardOutPath</key><string>#{xml_escape(log_path)}</string>
|
|
319
|
+
<key>StandardErrorPath</key><string>#{xml_escape(log_path)}</string>
|
|
320
|
+
</dict>
|
|
321
|
+
</plist>
|
|
322
|
+
XML
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def systemd_service_unit
|
|
326
|
+
args = [@hammer_bin, 'h:cron', "--port=#{@port}"]
|
|
327
|
+
args << "--pass=#{@pass}" if @pass
|
|
328
|
+
|
|
329
|
+
<<~UNIT
|
|
330
|
+
[Unit]
|
|
331
|
+
Description=#{systemd_quote("hammer h:cron job server (#{File.basename(@root_dir)})")}
|
|
332
|
+
|
|
333
|
+
[Service]
|
|
334
|
+
ExecStart=#{args.map { |arg| systemd_quote(arg) }.join(' ')}
|
|
335
|
+
WorkingDirectory=#{systemd_quote(@root_dir)}
|
|
336
|
+
Restart=on-failure
|
|
337
|
+
|
|
338
|
+
[Install]
|
|
339
|
+
WantedBy=default.target
|
|
340
|
+
UNIT
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def xml_escape(value)
|
|
344
|
+
value.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
|
345
|
+
.gsub('"', '"').gsub("'", ''')
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def systemd_quote(value)
|
|
349
|
+
escaped = value.to_s.gsub(/[\x00-\x1f\x7f%$\\"]/) do |char|
|
|
350
|
+
case char
|
|
351
|
+
when '%' then '%%'
|
|
352
|
+
when '$' then '$$'
|
|
353
|
+
when '\\' then '\\\\'
|
|
354
|
+
when '"' then '\\"'
|
|
355
|
+
when "\n" then '\\n'
|
|
356
|
+
when "\r" then '\\r'
|
|
357
|
+
when "\t" then '\\t'
|
|
358
|
+
else format('\\x%02x', char.ord)
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
%Q{"#{escaped}"}
|
|
362
|
+
end
|
|
363
|
+
|
|
329
364
|
# Refuse to start when the state file points at another live h:cron -
|
|
330
365
|
# two schedulers would double-fire every job. Best-effort (no lock
|
|
331
366
|
# file): kill(0) tells us whether that pid is still alive.
|