sockd 0.1.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 +7 -0
- data/.gitignore +14 -0
- data/.vagrant/machines/default/virtualbox/action_provision +1 -0
- data/.vagrant/machines/default/virtualbox/action_set_name +1 -0
- data/.vagrant/machines/default/virtualbox/id +1 -0
- data/.vagrant/machines/default/virtualbox/index_uuid +1 -0
- data/.vagrant/machines/default/virtualbox/private_key +27 -0
- data/.vagrant/machines/default/virtualbox/synced_folders +1 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/Vagrantfile +4 -0
- data/lib/sockd/errors.rb +15 -0
- data/lib/sockd/optparse.rb +117 -0
- data/lib/sockd/runner.rb +308 -0
- data/lib/sockd/version.rb +3 -0
- data/lib/sockd.rb +31 -0
- data/sockd.gemspec +24 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d0a446b2a9f0577de1dc8f750e0d49f913490f12
|
4
|
+
data.tar.gz: 0e6d76f5c7d9443182ed00888445be74080b1067
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cee27594593028ab2417be7cf4898fc3f6be8b0087bc54fe97a3dae408b212fc986ca3777efcba4f65ba88d0e493ecdffeb97fbbd88851e8fe61402cef0e6d83
|
7
|
+
data.tar.gz: c1c8c2f2ead721ba3b756802626ead69d5a3262931cd0255f3de4072a13b1cdab8cff3d4a4debf6516f10bb51ecfb4e78592a1684dc1329004367b178d01fc19
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.5:5b9af9bc-a6a4-4a38-bcee-3dd882a77565
|
@@ -0,0 +1 @@
|
|
1
|
+
1426302069
|
@@ -0,0 +1 @@
|
|
1
|
+
5b9af9bc-a6a4-4a38-bcee-3dd882a77565
|
@@ -0,0 +1 @@
|
|
1
|
+
c2db229506d4472b8fb6d1e0d697d20f
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEowIBAAKCAQEA7xjuC39O1pFg27/RWTeSTOwaGWqCYka1WpLBRd1nr/2mZw81
|
3
|
+
yjBGmWRwkD686TMZR+id5x+4bDTE9VasiZ3OU9EX6thL+8epVPdkBBRaQqluXHzW
|
4
|
+
soLZ1sP8GprJkTMEzUJyBPiVEl/feVW55yzTHWAgDUWWrp4qd7VvRmk4e0W25NRF
|
5
|
+
69EMMvHMaFneHYJZ9BiDh2w2OAM1ehdyHU4ruWm8UTjwEzpT98fJSsUhBcColtZT
|
6
|
+
J7+cN8ksSiaihUlbJjzSS36CWrX5jBXyE4RYCFSEoHzp3vdO756j9bQqfdqMLfS4
|
7
|
+
25C2azX6bQehlR3eSxK5CHixgzcWyyb2ZnT5TQIDAQABAoIBAQDB9BwuMXCXuEi1
|
8
|
+
Gi5NzDmesqwdT/xCko9M0N19ujQBXKae1YTR5kVu6z4wlOQT3AK3BWkJ8v5csJXR
|
9
|
+
WyUNYXjdHzHirOE+dmHTbfexI31wtBa1agOTvXfOsx8Pyd6XLabIhw0NIUV0KVeU
|
10
|
+
CsmKUR9UpgR6H6gzMFTM1N5WZMaPn5VatDCv8/1n3wju+7CHClPuPdYfAoNNTPY0
|
11
|
+
uIMkMVwguO9EjgNitbzfickQNT37UNyKXyqccVfdyvR7nnrs8QMIUH6Ny+dHwVei
|
12
|
+
+mkeOmEK8+vzCz/6XaRkgZ8lp3tKDRgaoUqIIK2zHGoS3YfugkbMhjI8E8NFpXVi
|
13
|
+
plNRzYQVAoGBAPyTEsXD/1qVvIibowPzA/kvb1QAzkdosxY2X5O7eYbGWB7bQSvt
|
14
|
+
hdp48/EalWOjp0CsUYCmXZRjU7BfIodbvVn87hAS2cc1BHqAgFmrx31jp/qfc4Zc
|
15
|
+
iqjJ/dnUto/yw/9ymg6uAc4dWQkzFsUj/gpEFJYuDsjZxCDFEe4DUUiLAoGBAPJX
|
16
|
+
EInRvuq6351Yq5YqHSuKhCxDAsF5Us+9dQqglQ5C7T/XuTb3bbgryvKU05FUVTQ4
|
17
|
+
zI6cRbDKkamOMK3wFdSX7It6h7VYqnuyb+BB2kx/GYkguI2Gh2jDvu0GS8RmnVOe
|
18
|
+
MzAACZW56jBCH8TIo1WyYbgnFsi5br9W/JrABiiHAoGAaaTKsVWttSH0Xd9bbAd1
|
19
|
+
ngCtPCGqJkwwvBW6HHzJ9Al6Sk3DELCk0CDPSAWqfqaxCmgJHxp3Ad4Z3yertZo1
|
20
|
+
tTIKEUcRODWAzgx5owgqxNOuKzIOjCChDsCOIVLLmaIdQPeaYJF/x8qWtzZbS9Im
|
21
|
+
K/0V70sMfA457sr/GOLiWMkCgYBG4tTBm4q+2n7CZcd7G0KBE/lpgQhzU2u3vHFZ
|
22
|
+
9HLlQwpm3FEfFO0RLC4nGwVvwz1REzQymt9/wSEueZsfpdSc5PuaKPM9RnxHxoAX
|
23
|
+
Fuwl6B+uDtYs/6boLCnS5z6Oan2mkeXjKn+jPkiUaSMlypKMZKtU1IpDmIoeifFF
|
24
|
+
ytmCTQKBgHOzJIaY5b/s0mRbBGKYPT7idRWT0a1XVvAsJOTTuYNsbzGpWGcNPr+n
|
25
|
+
YmO2cl10W4YntFG3+U7uZYdEIqNK+mc1hs801dKmGdJqeBGwf0N6memJMLRZG+HX
|
26
|
+
FI8PY96eiNVJfIjG/tORr443ziJTlWJJf//pOvHEtQE54erC6ocI
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1 @@
|
|
1
|
+
{"virtualbox":{"/tmp/vagrant-opsworks":{"guestpath":"/tmp/vagrant-opsworks","hostpath":"/Users/mike/.vagrant.d/boxes/ubuntu1404-opsworks/0/virtualbox","disabled":false},"/vagrant":{"guestpath":"/vagrant","hostpath":"/Users/mike/Projects/sockd","disabled":false}}}
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 PixelCog Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Sockd
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'sockd'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install sockd
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it ( https://github.com/[my-github-username]/sockd/fork )
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/Vagrantfile
ADDED
data/lib/sockd/errors.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "optparse"
|
3
|
+
require "shellwords"
|
4
|
+
require "sockd/errors"
|
5
|
+
|
6
|
+
module Sockd
|
7
|
+
class OptionParser
|
8
|
+
|
9
|
+
attr_accessor :name, :options, :callback
|
10
|
+
|
11
|
+
def initialize(name = nil, defaults = {}, &block)
|
12
|
+
@name = name || File.basename($0)
|
13
|
+
@options = defaults.replace({
|
14
|
+
host: "127.0.0.1",
|
15
|
+
port: 0,
|
16
|
+
socket: false,
|
17
|
+
daemonize: true,
|
18
|
+
pid_path: "/var/run/#{safe_name}.pid",
|
19
|
+
log_path: false,
|
20
|
+
force: false,
|
21
|
+
user: nil,
|
22
|
+
group: nil,
|
23
|
+
verbose: false
|
24
|
+
}.merge(defaults))
|
25
|
+
@callback = block if block_given?
|
26
|
+
end
|
27
|
+
|
28
|
+
def safe_name
|
29
|
+
name.gsub(/(^[0-9]*|[^0-9a-z])/i, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
def parser
|
33
|
+
@parser ||= ::OptionParser.new do |opts|
|
34
|
+
opts.summary_width = 25
|
35
|
+
opts.banner = <<-EOF.gsub /^[ ]{8}/, ''
|
36
|
+
Usage: #{name} [options] <command> [<message>]
|
37
|
+
|
38
|
+
Commands:
|
39
|
+
#{name} run server without forking
|
40
|
+
#{name} start start as a daemon
|
41
|
+
#{name} stop [-f] stop a running daemon
|
42
|
+
#{name} restart stop, then start the daemon
|
43
|
+
#{name} send <message> send a message to a running daemon
|
44
|
+
#{name} <message> send a message (command implied)
|
45
|
+
|
46
|
+
Options:
|
47
|
+
EOF
|
48
|
+
|
49
|
+
instance_exec(opts, callback) if callback
|
50
|
+
|
51
|
+
opts.on("-p", "--port PORT", String, "Listen on TCP port PORT (default: #{options[:port]})") do |x|
|
52
|
+
options[:port] = x
|
53
|
+
# prefer TCP connection if explicitly setting a port
|
54
|
+
options[:socket] = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on("-H", "--host HOST", String, "Listen on HOST (default: #{options[:host]})") do |x|
|
58
|
+
options[:host] = x
|
59
|
+
# prefer TCP connection if explicitly setting a host
|
60
|
+
options[:socket] = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("-s", "--socket [SOCKET]", String, "Listen on Unix socket path (disables network support)", "(default: #{options[:socket]})") do |x|
|
64
|
+
options[:socket] = File.expand_path(x)
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on("-P", "--pid FILE", String, "Where to write the PID file", "(default: #{options[:pid_path]})") do |x|
|
68
|
+
options[:pid_path] = File.expand_path(x)
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on("-l", "--log FILE", String, "Where to write the log file", "(default: #{options[:log_path]})") do |x|
|
72
|
+
options[:log_path] = File.expand_path(x)
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on("-u", "--user USER", String, "Assume the identity of USER when running as a daemon (default: #{options[:user]})") do |x|
|
76
|
+
options[:user] = x
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on("-g", "--group GROUP", String, "Assume group GROUP when running as a daemon (default: #{options[:group]})") do |x|
|
80
|
+
options[:group] = x
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on("-f", "--force", String, "Force kill if SIGTERM fails when running 'stop' command") do
|
84
|
+
options[:force] = true
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.separator "\n Additional Options:"
|
88
|
+
|
89
|
+
opts.on_tail("-h", "--help", "Display this usage information.") do
|
90
|
+
puts "\n#{opts}\n"
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse!(argv = nil)
|
97
|
+
argv ||= ARGV.dup
|
98
|
+
argv = Shellwords.shellwords argv if argv.is_a? String
|
99
|
+
|
100
|
+
parser.parse! argv
|
101
|
+
|
102
|
+
if argv.empty?
|
103
|
+
argv.push 'start'
|
104
|
+
options[:daemonize] = false
|
105
|
+
end
|
106
|
+
argv.unshift 'send' unless %w(start stop restart send).include?(argv.first)
|
107
|
+
|
108
|
+
argv
|
109
|
+
rescue ::OptionParser::InvalidOption, ::OptionParser::MissingArgument => e
|
110
|
+
raise OptionParserError.new e
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
parser.to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/sockd/runner.rb
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "socket"
|
3
|
+
require "fileutils"
|
4
|
+
require "sockd/errors"
|
5
|
+
|
6
|
+
module Sockd
|
7
|
+
class Runner
|
8
|
+
|
9
|
+
attr_reader :options, :name
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def define(*args, &block)
|
13
|
+
self.new(*args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(name, options = {}, &block)
|
18
|
+
@name = name
|
19
|
+
@options = {
|
20
|
+
:host => "127.0.0.1",
|
21
|
+
:port => 0,
|
22
|
+
:socket => false,
|
23
|
+
:daemonize => true,
|
24
|
+
:pid_path => "/var/run/#{safe_name}.pid",
|
25
|
+
:log_path => false,
|
26
|
+
:force => false,
|
27
|
+
:user => nil,
|
28
|
+
:group => nil
|
29
|
+
}.merge(options)
|
30
|
+
|
31
|
+
[:setup, :teardown, :handle].each do |opt|
|
32
|
+
self.public_send(opt, &options[opt]) if options[opt].respond_to?(:call)
|
33
|
+
end
|
34
|
+
|
35
|
+
yield self if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
# merge options when set with self.options = {...}
|
39
|
+
def options=(val)
|
40
|
+
@options.merge!(val)
|
41
|
+
end
|
42
|
+
|
43
|
+
# generate a path-safe and username-safe string from our daemon name
|
44
|
+
def safe_name
|
45
|
+
name.gsub(/(^[0-9]*|[^0-9a-z])/i, '')
|
46
|
+
end
|
47
|
+
|
48
|
+
# define a "setup" callback by providing a block, or trigger the callback
|
49
|
+
# @runner.setup { |opts| Server.new(...) }
|
50
|
+
def setup(&block)
|
51
|
+
return self if block_given? && @setup = block
|
52
|
+
@setup.call(self) if @setup
|
53
|
+
end
|
54
|
+
|
55
|
+
# define a "teardown" callback by providing a block, or trigger the callback
|
56
|
+
# @runner.teardown { log "shutting down" }
|
57
|
+
def teardown(&block)
|
58
|
+
return self if block_given? && @teardown = block
|
59
|
+
@teardown.call(self) if @teardown
|
60
|
+
end
|
61
|
+
|
62
|
+
# define our socket handler by providing a block, or trigger the callback
|
63
|
+
# with the provided message
|
64
|
+
# @runner.handle { |msg| if msg == 'foo' then return 'bar' ... }
|
65
|
+
def handle(message = nil, &block)
|
66
|
+
return self if block_given? && @handle = block
|
67
|
+
@handle || (raise SockdError, "No message handler provided.")
|
68
|
+
@handle.call(message, self)
|
69
|
+
end
|
70
|
+
|
71
|
+
# call one of start, stop, restart, or send
|
72
|
+
def run(method, *args)
|
73
|
+
if %w(start stop restart send).include?(method)
|
74
|
+
begin
|
75
|
+
self.public_send method.to_sym, *args
|
76
|
+
rescue ArgumentError => e
|
77
|
+
raise unless e.backtrace[1].include? "in `public_send"
|
78
|
+
raise BadCommandError, "wrong number of arguments for command: #{method}"
|
79
|
+
end
|
80
|
+
else
|
81
|
+
raise BadCommandError, "invalid command: #{method}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# start our service
|
86
|
+
def start
|
87
|
+
if options[:daemonize]
|
88
|
+
pid = daemon_running?
|
89
|
+
raise ProcError, "#{name} process already running (#{pid})" if pid
|
90
|
+
log "starting #{name} process..."
|
91
|
+
return self unless daemonize
|
92
|
+
end
|
93
|
+
|
94
|
+
drop_privileges options[:user], options[:group]
|
95
|
+
|
96
|
+
setup
|
97
|
+
|
98
|
+
on_interrupt do |signal|
|
99
|
+
log "#{signal} received, shutting down..."
|
100
|
+
teardown
|
101
|
+
exit 130
|
102
|
+
end
|
103
|
+
|
104
|
+
serve
|
105
|
+
end
|
106
|
+
|
107
|
+
# stop our service
|
108
|
+
def stop
|
109
|
+
if daemon_running?
|
110
|
+
pid = stored_pid
|
111
|
+
Process.kill('TERM', pid)
|
112
|
+
log "SIGTERM sent to #{name} (#{pid})"
|
113
|
+
if !wait_until(2) { daemon_stopped? pid } && options[:force]
|
114
|
+
Process.kill('KILL', pid)
|
115
|
+
log "SIGKILL sent to #{name} (#{pid})"
|
116
|
+
end
|
117
|
+
raise ProcError.new("unable to stop #{name} process") if daemon_running?
|
118
|
+
else
|
119
|
+
log "#{name} process not running"
|
120
|
+
end
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
# restart our service
|
125
|
+
def restart
|
126
|
+
stop
|
127
|
+
start
|
128
|
+
end
|
129
|
+
|
130
|
+
# send a message to a running service and return the response
|
131
|
+
def send(*args)
|
132
|
+
raise ArgumentError if args.empty?
|
133
|
+
message = args.join(' ')
|
134
|
+
response = nil
|
135
|
+
begin
|
136
|
+
client do |sock|
|
137
|
+
sock.write message + "\r\n"
|
138
|
+
response = sock.gets
|
139
|
+
end
|
140
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT
|
141
|
+
unless daemon_running?
|
142
|
+
abort "#{name} process not running"
|
143
|
+
end
|
144
|
+
abort "unable to establish connection"
|
145
|
+
end
|
146
|
+
puts response
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
# run a server loop, passing data off to our handler
|
152
|
+
def serve
|
153
|
+
server do |server|
|
154
|
+
log "listening on " + server.local_address.inspect_sockaddr
|
155
|
+
while 1
|
156
|
+
sock = server.accept
|
157
|
+
begin
|
158
|
+
# wait for input
|
159
|
+
if IO.select([sock], nil, nil, 2.0)
|
160
|
+
handle sock
|
161
|
+
else
|
162
|
+
log "connection timed out"
|
163
|
+
end
|
164
|
+
rescue Errno::EPIPE, Errno::ECONNRESET
|
165
|
+
log "connection broken"
|
166
|
+
end
|
167
|
+
sock.close unless sock.closed?
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# return a UNIXServer or TCPServer instance depending on config
|
173
|
+
def server(&block)
|
174
|
+
if options[:socket]
|
175
|
+
UNIXServer.open(options[:socket], &block)
|
176
|
+
else
|
177
|
+
TCPServer.open(options[:host], options[:port], &block)
|
178
|
+
end
|
179
|
+
rescue Errno::EACCES
|
180
|
+
sock = options[:socket] || "#{options[:host]}:#{options[:port]}"
|
181
|
+
raise ProcError, "unable to open socket: #{sock} (check permissions)"
|
182
|
+
end
|
183
|
+
|
184
|
+
# return a UNIXSocket or TCPSocket instance depending on config
|
185
|
+
def client(&block)
|
186
|
+
if options[:socket]
|
187
|
+
UNIXSocket.open(options[:socket], &block)
|
188
|
+
else
|
189
|
+
TCPSocket.open(options[:host], options[:port], &block)
|
190
|
+
end
|
191
|
+
rescue Errno::EACCES
|
192
|
+
sock = options[:socket] || "#{options[:host]}:#{options[:port]}"
|
193
|
+
raise ProcError, "unable to open socket: #{sock} (check permissions)"
|
194
|
+
end
|
195
|
+
|
196
|
+
# handle process termination signals
|
197
|
+
def on_interrupt(&block)
|
198
|
+
trap("INT") { yield "SIGINT" }
|
199
|
+
trap("QUIT") { yield "SIGQUIT" }
|
200
|
+
trap("TERM") { yield "SIGTERM" }
|
201
|
+
end
|
202
|
+
|
203
|
+
# daemonize a process. returns true from the forked process, false otherwise
|
204
|
+
def daemonize
|
205
|
+
|
206
|
+
# ensure pid file and log file are writable if provided
|
207
|
+
pid_path = options[:pid_path] ? writable_file(options[:pid_path]) : nil
|
208
|
+
log_path = options[:log_path] ? writable_file(options[:log_path]) : nil
|
209
|
+
|
210
|
+
unless fork
|
211
|
+
Process.setsid
|
212
|
+
exit if fork
|
213
|
+
File.umask 0000
|
214
|
+
Dir.chdir "/"
|
215
|
+
|
216
|
+
# save pid file
|
217
|
+
File.open(pid_path, 'w') { |f| f.write Process.pid } if pid_path
|
218
|
+
|
219
|
+
# redirect our io
|
220
|
+
setup_logging(log_path)
|
221
|
+
|
222
|
+
# trap and ignore SIGHUP
|
223
|
+
Signal.trap('HUP') {}
|
224
|
+
|
225
|
+
# trap reopen our log files on SIGUSR1
|
226
|
+
Signal.trap('USR1') { setup_logging(log_path) }
|
227
|
+
|
228
|
+
return true
|
229
|
+
end
|
230
|
+
|
231
|
+
Process.waitpid
|
232
|
+
unless wait_until { daemon_running? }
|
233
|
+
raise ProcError, "failed to start #{@name} service"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# returns the process id if a daemon is running with our pid file
|
238
|
+
def daemon_running?(pid = nil)
|
239
|
+
pid ||= stored_pid
|
240
|
+
Process.kill(0, pid) if pid
|
241
|
+
pid
|
242
|
+
rescue Errno::ESRCH
|
243
|
+
false
|
244
|
+
end
|
245
|
+
|
246
|
+
# reverse of daemon_running?
|
247
|
+
def daemon_stopped?(pid = nil)
|
248
|
+
!daemon_running? pid
|
249
|
+
end
|
250
|
+
|
251
|
+
# drop privileges to the specified user and group
|
252
|
+
def drop_privileges(user, group)
|
253
|
+
uid = Etc.getpwnam(user).uid if user
|
254
|
+
gid = Etc.getgrnam(group).gid if group
|
255
|
+
gid = Etc.getpwnam(user).gid if group.nil? && user
|
256
|
+
|
257
|
+
Process::Sys.setuid(uid) if uid
|
258
|
+
Process::Sys.setgid(gid) if gid
|
259
|
+
rescue ArgumentError => e
|
260
|
+
# user or group does not exist
|
261
|
+
raise ProcError, "unable to drop privileges (#{e})"
|
262
|
+
end
|
263
|
+
|
264
|
+
# redirect our output as per configuration
|
265
|
+
def setup_logging(log_path)
|
266
|
+
log_path ||= '/dev/null'
|
267
|
+
$stdin.reopen '/dev/null'
|
268
|
+
$stdout.reopen(log_path, 'a')
|
269
|
+
$stderr.reopen $stdout
|
270
|
+
$stdout.sync = true
|
271
|
+
end
|
272
|
+
|
273
|
+
# returns the pid stored in our pid_path
|
274
|
+
def stored_pid
|
275
|
+
return false unless options[:pid_path]
|
276
|
+
path = File.expand_path(options[:pid_path])
|
277
|
+
return false unless File.file?(path) && !File.zero?(path)
|
278
|
+
File.read(path).chomp.to_i
|
279
|
+
end
|
280
|
+
|
281
|
+
# ensure a writable file exists at the specified path
|
282
|
+
def writable_file(path)
|
283
|
+
path = File.expand_path(path)
|
284
|
+
begin
|
285
|
+
FileUtils.mkdir_p(File.dirname(path), :mode => 0755)
|
286
|
+
FileUtils.touch path
|
287
|
+
File.chmod(0644, path)
|
288
|
+
rescue Errno::EACCES, Errno::EISDIR
|
289
|
+
end
|
290
|
+
unless File.file?(path) && File.writable?(path)
|
291
|
+
raise ProcError, "unable to open file: #{path} (check permissions)"
|
292
|
+
end
|
293
|
+
path
|
294
|
+
end
|
295
|
+
|
296
|
+
def wait_until(timer = 5, interval = 0.1, &block)
|
297
|
+
until timer < 0 or block.call
|
298
|
+
timer -= interval
|
299
|
+
sleep interval
|
300
|
+
end
|
301
|
+
timer > 0
|
302
|
+
end
|
303
|
+
|
304
|
+
def log(message)
|
305
|
+
puts Time.now.strftime('%Y-%m-%d %H:%M:%S: ') + message
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
data/lib/sockd.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "sockd/errors"
|
2
|
+
require "sockd/optparse"
|
3
|
+
require "sockd/runner"
|
4
|
+
require "sockd/version"
|
5
|
+
|
6
|
+
module Sockd
|
7
|
+
|
8
|
+
# instantiate a new sockd service
|
9
|
+
def self.define(name, options = {}, &block)
|
10
|
+
Runner.define(name, options, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# instantiate command line option parser
|
14
|
+
def self.optparse(name, defaults = {}, &block)
|
15
|
+
OptionParser.new(name, defaults, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# instantiate and run a sockd service using command line arguments
|
19
|
+
def self.run(name, options = {}, &block)
|
20
|
+
parser = optparse(name, options)
|
21
|
+
argv = parser.parse!
|
22
|
+
define(name, options, &block).run(*argv)
|
23
|
+
rescue OptionParserError, BadCommandError => e
|
24
|
+
warn "Error: #{e.message}"
|
25
|
+
warn "#{parser}\n"
|
26
|
+
exit
|
27
|
+
rescue SockdError => e
|
28
|
+
warn "Error: #{e.message}"
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
end
|
data/sockd.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sockd/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "sockd"
|
8
|
+
s.version = Sockd::VERSION
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.summary = "A framework for single-threaded ruby socket daemons"
|
12
|
+
s.description = "Sockd makes it easy to create a single-threaded daemon which can listen on a TCP or Unix socket and respond to commands"
|
13
|
+
|
14
|
+
s.authors = ["Mike Greiling"]
|
15
|
+
s.email = "mike@pixelcog.com"
|
16
|
+
s.homepage = "http://pixelcog.com/"
|
17
|
+
|
18
|
+
s.files = `git ls-files -z`.split("\x0")
|
19
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency "bundler", "~> 1.7"
|
23
|
+
s.add_development_dependency "rake", "~> 10.0"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sockd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Greiling
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Sockd makes it easy to create a single-threaded daemon which can listen
|
42
|
+
on a TCP or Unix socket and respond to commands
|
43
|
+
email: mike@pixelcog.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- .vagrant/machines/default/virtualbox/action_provision
|
50
|
+
- .vagrant/machines/default/virtualbox/action_set_name
|
51
|
+
- .vagrant/machines/default/virtualbox/id
|
52
|
+
- .vagrant/machines/default/virtualbox/index_uuid
|
53
|
+
- .vagrant/machines/default/virtualbox/private_key
|
54
|
+
- .vagrant/machines/default/virtualbox/synced_folders
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- Vagrantfile
|
60
|
+
- lib/sockd.rb
|
61
|
+
- lib/sockd/errors.rb
|
62
|
+
- lib/sockd/optparse.rb
|
63
|
+
- lib/sockd/runner.rb
|
64
|
+
- lib/sockd/version.rb
|
65
|
+
- sockd.gemspec
|
66
|
+
homepage: http://pixelcog.com/
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.0.14
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: A framework for single-threaded ruby socket daemons
|
90
|
+
test_files: []
|