launchr 1.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.
- data/.document +5 -0
- data/.gitignore +44 -0
- data/.yardopts +4 -0
- data/Gemfile +24 -0
- data/LICENSE +20 -0
- data/README.rdoc +63 -0
- data/Rakefile +78 -0
- data/VERSION +1 -0
- data/bin/brew-launchd.rb +24 -0
- data/bin/brew-restart.rb +22 -0
- data/bin/brew-start.rb +24 -0
- data/bin/brew-stop.rb +24 -0
- data/ext/Makefile +157 -0
- data/ext/extconf.rb +14 -0
- data/ext/launchd-socket-listener-unload.cpp +160 -0
- data/features/launchr.feature +9 -0
- data/features/step_definitions/launchr_steps.rb +0 -0
- data/features/support/env.rb +14 -0
- data/lib/launchr.rb +105 -0
- data/lib/launchr/application.rb +24 -0
- data/lib/launchr/cli.rb +142 -0
- data/lib/launchr/commands.rb +118 -0
- data/lib/launchr/extend/pathname.rb +27 -0
- data/lib/launchr/mixin/mixlib_cli.rb +693 -0
- data/lib/launchr/mixin/ordered_hash.rb +189 -0
- data/lib/launchr/mixin/popen4.rb +219 -0
- data/lib/launchr/path.rb +106 -0
- data/lib/launchr/service.rb +522 -0
- data/man1/brew-launchd.1 +95 -0
- data/man1/brew-launchd.1.html +140 -0
- data/man1/brew-launchd.1.ronn +71 -0
- data/spec/launchr/application_spec.rb +37 -0
- data/spec/launchr/cli_spec.rb +25 -0
- data/spec/launchr/commands_spec.rb +20 -0
- data/spec/launchr/config_spec.rb +38 -0
- data/spec/launchr_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +216 -0
data/ext/extconf.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
create_makefile("launchd-socket-listener-unload")
|
5
|
+
|
6
|
+
makefile = File.read("Makefile")
|
7
|
+
makefile.gsub!(/-bundle ?/,"")
|
8
|
+
makefile.gsub!(/\$\(TARGET\)\.bundle/,"$(TARGET)")
|
9
|
+
|
10
|
+
File.open("Makefile","w") do |f|
|
11
|
+
f.puts makefile
|
12
|
+
end
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,160 @@
|
|
1
|
+
//
|
2
|
+
// launchd-socket-listener-unload.cpp
|
3
|
+
// http://fazekasmiklos.blogspot.com/2007/04/howto-unload-launchd-deamon-from-itself.html
|
4
|
+
//
|
5
|
+
// Instructions by Dreamcat4 (dreamcat4@gmail.com)
|
6
|
+
//
|
7
|
+
// To compile:
|
8
|
+
// $ g++ launchd-socket-listener-unload.cpp -o launchd-socket-listener-unload
|
9
|
+
//
|
10
|
+
// A small program which unloads the launchd job that started it
|
11
|
+
// Use with the below configuration to trigger the real job to load
|
12
|
+
//
|
13
|
+
// Create a plist file for this daemon called "launchd.myjob.label-loader.plist"
|
14
|
+
// Put the listener socket definitions here
|
15
|
+
//
|
16
|
+
//
|
17
|
+
// <key>Program</key>
|
18
|
+
// <string>/path/to/launchd-socket-listener-unload</string>
|
19
|
+
// <key>ServiceIPC</key>
|
20
|
+
// <true/>
|
21
|
+
// <key>Sockets</key>
|
22
|
+
// <dict>
|
23
|
+
// <key>Listeners</key>
|
24
|
+
// <dict>
|
25
|
+
// <!-- Service name (/etc/services), or a tcp port number -->
|
26
|
+
// <!-- or can be anything else launchd supports (udp,etc) -->
|
27
|
+
// <key>SockServiceName</key>
|
28
|
+
// <string>8080</string>
|
29
|
+
// </dict>
|
30
|
+
// </dict>
|
31
|
+
//
|
32
|
+
// You probably already have made a 2nd plist file called "launchd_myjob_label.plist"
|
33
|
+
// Add to this file a KeepAlive directive, which depends on the first job not running
|
34
|
+
//
|
35
|
+
// keep alive -> other job enabled -> false
|
36
|
+
//
|
37
|
+
// <key>KeepAlive</key>
|
38
|
+
// <dict>
|
39
|
+
// <key>OtherJobEnabled</key>
|
40
|
+
// <dict>
|
41
|
+
// <key>launchd.myjob.label-loader</key>
|
42
|
+
// <false/>
|
43
|
+
// </dict>
|
44
|
+
// </dict>
|
45
|
+
//
|
46
|
+
//
|
47
|
+
// This job will then start when the first job is unloaded, which is
|
48
|
+
// whenever the first job is triggered by one of the listener sockets
|
49
|
+
//
|
50
|
+
// Bear in mind,
|
51
|
+
// The very first http request / connection attempt will be dropped
|
52
|
+
// So a connecting client will have to try again in order to establish
|
53
|
+
// a connection to the real job.
|
54
|
+
|
55
|
+
#include <stdlib.h>
|
56
|
+
#include <errno.h>
|
57
|
+
#include <syslog.h>
|
58
|
+
|
59
|
+
#include "launch.h"
|
60
|
+
|
61
|
+
class LaunchD
|
62
|
+
{
|
63
|
+
public:
|
64
|
+
LaunchD()
|
65
|
+
{
|
66
|
+
startedWithLaunchD = false;
|
67
|
+
me = 0;
|
68
|
+
CheckIn();
|
69
|
+
}
|
70
|
+
|
71
|
+
~LaunchD()
|
72
|
+
{
|
73
|
+
if (me) {
|
74
|
+
launch_data_free(me);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
bool CheckIn(bool allowRunWithoutLaunchd = true)
|
79
|
+
{
|
80
|
+
launch_data_t msg, resp;
|
81
|
+
msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
|
82
|
+
if ((resp = launch_msg(msg)) == NULL) {
|
83
|
+
if (allowRunWithoutLaunchd) {
|
84
|
+
startedWithLaunchD = false;
|
85
|
+
return false;
|
86
|
+
}
|
87
|
+
syslog(LOG_ERR,"Checkin with launchd failed: %m");
|
88
|
+
exit(EXIT_FAILURE);
|
89
|
+
}
|
90
|
+
launch_data_free(msg);
|
91
|
+
if (LAUNCH_DATA_ERRNO == launch_data_get_type(resp)) {
|
92
|
+
errno = launch_data_get_errno(resp);
|
93
|
+
if (errno == EACCES) {
|
94
|
+
syslog(LOG_ERR, "Check-in failed. Did you forget to set"
|
95
|
+
"ServiceIPC == true in your plist?");
|
96
|
+
} else {
|
97
|
+
syslog(LOG_ERR, "Check-in failed: %m");
|
98
|
+
}
|
99
|
+
exit(EXIT_FAILURE);
|
100
|
+
}
|
101
|
+
launch_data_t tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_LABEL);
|
102
|
+
me = launch_data_copy(tmp);
|
103
|
+
if(me)
|
104
|
+
{
|
105
|
+
syslog(LOG_ERR, "%s triggered\n",job_label());
|
106
|
+
}
|
107
|
+
startedWithLaunchD = true;
|
108
|
+
return true;
|
109
|
+
}
|
110
|
+
|
111
|
+
const char* job_label()
|
112
|
+
{
|
113
|
+
return launch_data_get_string(me);
|
114
|
+
}
|
115
|
+
|
116
|
+
bool Stop()
|
117
|
+
{
|
118
|
+
if (startedWithLaunchD) {
|
119
|
+
launch_data_t resp;
|
120
|
+
launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
|
121
|
+
if (! launch_data_dict_insert(msg,me,LAUNCH_KEY_REMOVEJOB)) {
|
122
|
+
syslog(LOG_ERR, "launch_data_dict_insert failed!\n");
|
123
|
+
return false;
|
124
|
+
}
|
125
|
+
if (! launch_data_dict_insert(msg,me,LAUNCH_KEY_STOPJOB)) {
|
126
|
+
syslog(LOG_ERR, "launch_data_dict_insert failed!\n");
|
127
|
+
return false;
|
128
|
+
}
|
129
|
+
syslog(LOG_ERR, "%s unloaded\n", job_label());
|
130
|
+
resp = launch_msg(msg);
|
131
|
+
syslog(LOG_ERR, "%s ...not unloaded. Failure when trying to unload %s.\n", job_label(), job_label());
|
132
|
+
if (resp == NULL) {
|
133
|
+
syslog(LOG_ERR, "launch_msg() LAUNCH_KEY_STOPJOB failed!\n");
|
134
|
+
return false;
|
135
|
+
}
|
136
|
+
if (LAUNCH_DATA_ERRNO == launch_data_get_type(resp)) {
|
137
|
+
errno = launch_data_get_errno(resp);
|
138
|
+
if (errno == EACCES) {
|
139
|
+
syslog(LOG_ERR, "Stop request failed EACCESS!");
|
140
|
+
} else {
|
141
|
+
syslog(LOG_ERR, "Check-in failed: %m");
|
142
|
+
}
|
143
|
+
exit(EXIT_FAILURE);
|
144
|
+
}
|
145
|
+
launch_data_free(msg);
|
146
|
+
}
|
147
|
+
return true;
|
148
|
+
}
|
149
|
+
private:
|
150
|
+
bool startedWithLaunchD;
|
151
|
+
launch_data_t me;
|
152
|
+
};
|
153
|
+
|
154
|
+
|
155
|
+
int main()
|
156
|
+
{
|
157
|
+
LaunchD ld;
|
158
|
+
ld.Stop();
|
159
|
+
}
|
160
|
+
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
begin
|
3
|
+
Bundler.setup(:runtime, :development)
|
4
|
+
rescue Bundler::BundlerError => e
|
5
|
+
$stderr.puts e.message
|
6
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
7
|
+
exit e.status_code
|
8
|
+
end
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
11
|
+
require 'launchr'
|
12
|
+
|
13
|
+
require 'spec/expectations'
|
14
|
+
|
data/lib/launchr.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
dir = File.dirname(__FILE__)
|
3
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
4
|
+
|
5
|
+
require 'launchr/path'
|
6
|
+
|
7
|
+
module Launchr
|
8
|
+
class << self
|
9
|
+
def config
|
10
|
+
@config ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_default
|
14
|
+
if Launchr::Path.launchr_default_boot.exist?
|
15
|
+
config[:boot] = true
|
16
|
+
else
|
17
|
+
config[:boot] = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def import_args
|
22
|
+
if config[:args][:user]
|
23
|
+
config[:boot] = nil
|
24
|
+
|
25
|
+
elsif config[:args][:boot]
|
26
|
+
config[:boot] = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def label
|
31
|
+
# "com.github.homebrew.launchr"
|
32
|
+
"com.github.launchr"
|
33
|
+
end
|
34
|
+
|
35
|
+
def launchctl_timeout
|
36
|
+
5
|
37
|
+
end
|
38
|
+
|
39
|
+
def real_user
|
40
|
+
Etc.getpwuid(Process.uid).name
|
41
|
+
end
|
42
|
+
|
43
|
+
def real_group
|
44
|
+
Etc.getgrgid(Process.gid).name
|
45
|
+
end
|
46
|
+
|
47
|
+
def superuser?
|
48
|
+
real_user == "root"
|
49
|
+
end
|
50
|
+
|
51
|
+
def sudo_user
|
52
|
+
if ENV["SUDO_USER"]
|
53
|
+
ENV["SUDO_USER"]
|
54
|
+
elsif ENV["SUDO_UID"]
|
55
|
+
Etc.getpwuid(ENV["SUDO_UID"].to_i).name
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def sudo_group
|
62
|
+
if ENV["SUDO_GROUP"]
|
63
|
+
ENV["SUDO_GROUP"]
|
64
|
+
elsif ENV["SUDO_GID"]
|
65
|
+
Etc.getgrgid(ENV["SUDO_GID"].to_i).name
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def user
|
72
|
+
if superuser?
|
73
|
+
sudo_user || real_user
|
74
|
+
else
|
75
|
+
real_user
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def group
|
80
|
+
if superuser?
|
81
|
+
sudo_group || real_group
|
82
|
+
else
|
83
|
+
real_group
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def uid
|
88
|
+
Etc.getpwnam(user).uid
|
89
|
+
end
|
90
|
+
|
91
|
+
def gid
|
92
|
+
Etc.getgrnam(group).gid
|
93
|
+
end
|
94
|
+
|
95
|
+
def version
|
96
|
+
if Launchr::Path.launchr_version.exist?
|
97
|
+
Launchr::Path.launchr_version.read.strip
|
98
|
+
else
|
99
|
+
"0.0.0"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require 'launchr/cli'
|
3
|
+
require 'launchr/commands'
|
4
|
+
|
5
|
+
module Launchr
|
6
|
+
# The Launchr Application Object. Instantiated for command-line mode
|
7
|
+
# @see Launchr::CLI
|
8
|
+
class Application
|
9
|
+
|
10
|
+
def initialize *args, &blk
|
11
|
+
@cli = Launchr::CLI.new
|
12
|
+
|
13
|
+
Launchr.load_default
|
14
|
+
|
15
|
+
Launchr.config[:args] = @cli.parse
|
16
|
+
Launchr.import_args
|
17
|
+
|
18
|
+
@commands = Launchr::Commands.new
|
19
|
+
@commands.run
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
data/lib/launchr/cli.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
|
2
|
+
require 'launchr/mixin/mixlib_cli'
|
3
|
+
|
4
|
+
module Launchr
|
5
|
+
# Defines options for the +launchr+ command line utility
|
6
|
+
class CLI
|
7
|
+
include Launchr::Mixlib::CLI
|
8
|
+
|
9
|
+
# The Launchr CLI Options
|
10
|
+
def self.launchr_cli_options
|
11
|
+
|
12
|
+
spaced_summary true
|
13
|
+
# banner false
|
14
|
+
summary_indent ""
|
15
|
+
summary_width 29
|
16
|
+
|
17
|
+
header "brew launchd - an extension to start and stop Launchd services."
|
18
|
+
header " `man brew-launchd` for more information"
|
19
|
+
header ""
|
20
|
+
banner "Usage: brew launchd [options]"
|
21
|
+
|
22
|
+
argument :start,
|
23
|
+
:long => "start service,(s)",
|
24
|
+
:type => Array,
|
25
|
+
:description => ["Start launchd service(s)",
|
26
|
+
"Equivalent to launchctl load -w files..."],
|
27
|
+
:example => "start dnsmasq memcached couchdb",
|
28
|
+
:default => nil
|
29
|
+
|
30
|
+
argument :stop,
|
31
|
+
:long => "stop service,(s)",
|
32
|
+
:type => Array,
|
33
|
+
:description => ["Stop launchd service(s)",
|
34
|
+
"Equivalent to launchctl unload -w files..."],
|
35
|
+
:example => "stop mamcached dnsmasq",
|
36
|
+
:default => nil
|
37
|
+
|
38
|
+
argument :restart,
|
39
|
+
:long => "restart service,(s)",
|
40
|
+
:type => Array,
|
41
|
+
:description => ["Restart launchd service(s)"],
|
42
|
+
:example => "restart couchdb",
|
43
|
+
:default => nil
|
44
|
+
|
45
|
+
option :user,
|
46
|
+
:indent => true,
|
47
|
+
:long => "--user",
|
48
|
+
:description => ["At user login.",
|
49
|
+
"Otherwise, the default setting will be used."],
|
50
|
+
:example => "start --user openvpn ddclient",
|
51
|
+
:requires => Proc.new { |args| (args[:boot] ^ args[:user]) && true || raise("--boot|--user") },
|
52
|
+
:default => nil
|
53
|
+
|
54
|
+
option :boot,
|
55
|
+
:indent => true,
|
56
|
+
:long => "--boot",
|
57
|
+
:description => ["At boot time. Requires sudo/root privelidges.",
|
58
|
+
"Otherwise, the default setting will be used."],
|
59
|
+
:example => "sudo brew start --boot nginx mysql",
|
60
|
+
:requires => Proc.new { |args| (args[:boot] ^ args[:user]) && true || raise("--boot|--user") },
|
61
|
+
:default => nil
|
62
|
+
|
63
|
+
|
64
|
+
argument :info,
|
65
|
+
:long => "info [service,(s)]",
|
66
|
+
:type => Array,
|
67
|
+
:proc => Proc.new { |l| (l == true) ? [] : l },
|
68
|
+
:description => ["Info for launchd service(s)","With no arguments prints info for all services."],
|
69
|
+
:example => "brew launchd info",
|
70
|
+
:default => nil
|
71
|
+
|
72
|
+
argument :clean,
|
73
|
+
:long => "clean",
|
74
|
+
:description => ["Clean missing/broken launchd service(s)."],
|
75
|
+
:example => ["brew launchd clean", "sudo brew launchd clean"],
|
76
|
+
:default => nil
|
77
|
+
|
78
|
+
argument :default,
|
79
|
+
:long => "default [--user|--boot]",
|
80
|
+
:description => [
|
81
|
+
"Set the default target to start launchd services.",
|
82
|
+
"The initial setting, --user will start daemons at",
|
83
|
+
"user login - from the Loginwindow (not over ssh).",
|
84
|
+
" ",
|
85
|
+
"Wheras --boot will set services to start at boot",
|
86
|
+
"time. But be aware that brew should be installed",
|
87
|
+
"to the root filesystem, not on a mounted volume."],
|
88
|
+
|
89
|
+
:example => [
|
90
|
+
"brew launchd default --boot",
|
91
|
+
"brew launchd default --user"
|
92
|
+
],
|
93
|
+
:requires => Proc.new { |args| (args[:boot] ^ args[:user]) && true || raise("--boot|--user") },
|
94
|
+
:default => nil
|
95
|
+
|
96
|
+
option :help,
|
97
|
+
:long => "--help",
|
98
|
+
:description => "Show this message",
|
99
|
+
:show_options => true,
|
100
|
+
:exit => 0
|
101
|
+
|
102
|
+
option :version,
|
103
|
+
:long => "--version",
|
104
|
+
:description => "Print version information",
|
105
|
+
:default => nil
|
106
|
+
end
|
107
|
+
launchr_cli_options
|
108
|
+
|
109
|
+
def parse argv=ARGV
|
110
|
+
parse_options(argv)
|
111
|
+
|
112
|
+
[:start,:stop,:restart].each do |cmd|
|
113
|
+
case config[cmd]
|
114
|
+
when Array
|
115
|
+
[:user, :boot].each do |level|
|
116
|
+
if config[cmd].include? "--#{level}"
|
117
|
+
config[level] = true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
config[cmd] -= ["--user","--boot"]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
raise "Please choose one of --user|--boot" if config[:user] && config[:boot]
|
125
|
+
|
126
|
+
unless filtered_argv.empty?
|
127
|
+
start_stop_restart_value = [config[:start],config[:stop],config[:restart],config[:info]].compact!
|
128
|
+
|
129
|
+
if start_stop_restart_value.size == 1
|
130
|
+
services = *start_stop_restart_value
|
131
|
+
extra_services = filtered_argv
|
132
|
+
|
133
|
+
services << extra_services
|
134
|
+
services.flatten!
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
config
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|