launchr 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|