Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a87a882e60 | ||
|
|
6ba9252d0f | ||
|
|
efd9e2119b | ||
|
|
bf9bdbf118 | ||
|
|
929a138e54 | ||
|
|
d6514b4f33 | ||
|
|
8696a36833 | ||
|
|
3ea5de42aa | ||
|
|
4a13122082 | ||
|
|
9b2987c3f0 | ||
|
|
6274f99225 | ||
|
|
0b0324fed9 | ||
|
|
32db70b778 | ||
|
|
3db1ad6fbc | ||
|
|
003b466a17 | ||
|
|
2a896a0fb5 | ||
|
|
6a8c81a38b | ||
|
|
a83dab363e | ||
|
|
53e0f4ecf9 | ||
|
|
e0fe5baf1b | ||
|
|
1aa1f15b8f | ||
|
|
2fcb64959b | ||
|
|
a5465bf55e |
@@ -1,3 +1,8 @@
|
|||||||
|
## 0.46.0 (2012-05-02)
|
||||||
|
|
||||||
|
* Add Profile load/write/append API [Michael Granger]
|
||||||
|
* Guard against missing Procfile in engine.rb [Brian Kaney]
|
||||||
|
|
||||||
## 0.45.0 (2012-04-26)
|
## 0.45.0 (2012-04-26)
|
||||||
|
|
||||||
* create and chown log dir in upstart export. [Phil Hagelberg]
|
* create and chown log dir in upstart export. [Phil Hagelberg]
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
foreman (0.45.0)
|
foreman (0.47.0)
|
||||||
thor (>= 0.13.6)
|
thor (>= 0.13.6)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ Manage Procfile-based applications
|
|||||||
* [wiki](http://github.com/ddollar/foreman/wiki)
|
* [wiki](http://github.com/ddollar/foreman/wiki)
|
||||||
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
* [shoreman](https://github.com/hecticjeff/shoreman) - shell
|
||||||
|
* [honcho](https://github.com/nickstenning/honcho) - python
|
||||||
|
* [norman](https://github.com/josh/norman) - node.js
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
#### Created and maintained by
|
#### Created and maintained by
|
||||||
|
|||||||
+3
-7
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
#
|
#
|
||||||
#/ Usage: foreman-runner [-d <dir>] <command>
|
#/ Usage: foreman-runner [-d <dir>] <command> [<args>...]
|
||||||
#/
|
#/
|
||||||
#/ Run a command with exec, optionally changing directory first
|
#/ Run a command with exec, optionally changing directory first
|
||||||
|
|
||||||
@@ -27,10 +27,6 @@ done
|
|||||||
|
|
||||||
shift $((OPTIND-1))
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
command=$1
|
[ -z "$1" ] && usage
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
exec "$@"
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec $1
|
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|||||||
|
|
||||||
process.working_dir = "<%= engine.directory %>"
|
process.working_dir = "<%= engine.directory %>"
|
||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "<%= port %>"}
|
process.environment = {"PORT" => "<%= port %>"<% engine.environment.each_pair do |var,env| %> , "<%= var.upcase %>" => "<%= env %>" <% end %>}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||||
|
process.stop_grace_time = 45.seconds
|
||||||
|
|
||||||
process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
|
process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string><%= "#{app}-#{process.name}-#{num}" %></string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string><%= process.command %></string>
|
||||||
|
</array>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string><%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string><%= user %></string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string><%= engine.directory %></string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -2,4 +2,4 @@ start on starting <%= app %>-<%= process.name %>
|
|||||||
stop on stopping <%= app %>-<%= process.name %>
|
stop on stopping <%= app %>-<%= process.name %>
|
||||||
respawn
|
respawn
|
||||||
|
|
||||||
exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= env %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
|
exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
|
||||||
|
|||||||
+3
-2
@@ -2,6 +2,7 @@ require "foreman"
|
|||||||
require "foreman/helpers"
|
require "foreman/helpers"
|
||||||
require "foreman/engine"
|
require "foreman/engine"
|
||||||
require "foreman/export"
|
require "foreman/export"
|
||||||
|
require "shellwords"
|
||||||
require "thor"
|
require "thor"
|
||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
@@ -59,12 +60,12 @@ class Foreman::CLI < Thor
|
|||||||
puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "run COMMAND", "Run a command using your application's environment"
|
desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
|
||||||
|
|
||||||
def run(*args)
|
def run(*args)
|
||||||
engine.apply_environment!
|
engine.apply_environment!
|
||||||
begin
|
begin
|
||||||
exec args.join(" ")
|
exec args.shelljoin
|
||||||
rescue Errno::EACCES
|
rescue Errno::EACCES
|
||||||
error "not executable: #{args.first}"
|
error "not executable: #{args.first}"
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class Foreman::Engine
|
|||||||
|
|
||||||
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
||||||
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
||||||
|
trap("HUP") { puts "SIGHUP received"; terminate_gracefully }
|
||||||
|
|
||||||
assign_colors
|
assign_colors
|
||||||
spawn_processes
|
spawn_processes
|
||||||
@@ -84,7 +85,7 @@ private ######################################################################
|
|||||||
end
|
end
|
||||||
|
|
||||||
def base_port
|
def base_port
|
||||||
options[:port] || 5000
|
options[:port] || environment["PORT"] || ENV["PORT"] || 5000
|
||||||
end
|
end
|
||||||
|
|
||||||
def kill_all(signal="SIGTERM")
|
def kill_all(signal="SIGTERM")
|
||||||
|
|||||||
@@ -31,4 +31,5 @@ require "foreman/export/upstart"
|
|||||||
require "foreman/export/bluepill"
|
require "foreman/export/bluepill"
|
||||||
require "foreman/export/runit"
|
require "foreman/export/runit"
|
||||||
require "foreman/export/supervisord"
|
require "foreman/export/supervisord"
|
||||||
|
require "foreman/export/launchd"
|
||||||
|
|
||||||
|
|||||||
@@ -48,4 +48,19 @@ private ######################################################################
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Quote a string to be used on the command line. Backslashes are escapde to \\ and quotes
|
||||||
|
# escaped to \"
|
||||||
|
#
|
||||||
|
# str - string to be quoted
|
||||||
|
#
|
||||||
|
# Examples
|
||||||
|
#
|
||||||
|
# shell_quote("FB|123\"\\1")
|
||||||
|
# # => "\"FB|123\"\\"\\\\1\""
|
||||||
|
#
|
||||||
|
# Returns the the escaped string surrounded by quotes
|
||||||
|
def shell_quote(str)
|
||||||
|
"\"#{str.gsub(/\\/){ '\\\\' }.gsub(/["]/){ "\\\"" }}\""
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
require "erb"
|
||||||
|
require "foreman/export"
|
||||||
|
|
||||||
|
class Foreman::Export::Launchd < Foreman::Export::Base
|
||||||
|
|
||||||
|
def export
|
||||||
|
error("Must specify a location") unless location
|
||||||
|
|
||||||
|
app = self.app || File.basename(engine.directory)
|
||||||
|
user = self.user || app
|
||||||
|
log_root = self.log || "/var/log/#{app}"
|
||||||
|
template_root = self.template
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(location)
|
||||||
|
|
||||||
|
engine.procfile.entries.each do |process|
|
||||||
|
1.upto(self.concurrency[process.name]) do |num|
|
||||||
|
|
||||||
|
master_template = export_template("launchd", "launchd.plist.erb", template_root)
|
||||||
|
master_config = ERB.new(master_template).result(binding)
|
||||||
|
write_file "#{location}/#{app}-#{process.name}-#{num}.plist", master_config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -62,7 +62,7 @@ private
|
|||||||
$stdout.reopen writer
|
$stdout.reopen writer
|
||||||
$stderr.reopen writer
|
$stderr.reopen writer
|
||||||
reader.close
|
reader.close
|
||||||
exec Foreman.runner, "-d", basedir, command
|
exec Foreman.runner, "-d", basedir, *command.shellsplit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
[ reader, pid ]
|
[ reader, pid ]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
module Foreman
|
module Foreman
|
||||||
|
|
||||||
VERSION = "0.46.0"
|
VERSION = "0.47.0"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
.\" generated with Ronn/v0.7.3
|
.\" generated with Ronn/v0.7.3
|
||||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||||
.
|
.
|
||||||
.TH "FOREMAN" "1" "April 2012" "Foreman 0.45.0" "Foreman Manual"
|
.TH "FOREMAN" "1" "April 2012" "Foreman 0.46.0" "Foreman Manual"
|
||||||
.
|
.
|
||||||
.SH "NAME"
|
.SH "NAME"
|
||||||
\fBforeman\fR \- manage Procfile\-based applications
|
\fBforeman\fR \- manage Procfile\-based applications
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ describe "Foreman::CLI", :fakefs do
|
|||||||
before { write_procfile }
|
before { write_procfile }
|
||||||
|
|
||||||
describe "and a command" do
|
describe "and a command" do
|
||||||
let(:command) { ["ls", "-l"] }
|
let(:command) { ["ls", "-l", "foo bar"] }
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
stub(subject).exec
|
stub(subject).exec
|
||||||
@@ -160,8 +160,8 @@ describe "Foreman::CLI", :fakefs do
|
|||||||
ENV["FOO"].should be_nil
|
ENV["FOO"].should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should runute the command as a string" do
|
it "should exec the argument list as a shell command" do
|
||||||
mock(subject).exec(command.join(" "))
|
mock(subject).exec(command.shelljoin)
|
||||||
subject.run *command
|
subject.run *command
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -100,6 +100,13 @@ describe "Foreman::Engine", :fakefs do
|
|||||||
engine.start
|
engine.start
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should set port from .env if specified" do
|
||||||
|
File.open(".env", "w") { |f| f.puts("PORT=8017") }
|
||||||
|
engine = Foreman::Engine.new("Procfile")
|
||||||
|
engine.send(:base_port).should == "8017"
|
||||||
|
engine.start
|
||||||
|
end
|
||||||
|
|
||||||
it "should be loaded relative to the Procfile" do
|
it "should be loaded relative to the Procfile" do
|
||||||
FileUtils.mkdir_p "/some/app"
|
FileUtils.mkdir_p "/some/app"
|
||||||
File.open("/some/app/.env", "w") { |f| f.puts("FOO=qoo") }
|
File.open("/some/app/.env", "w") { |f| f.puts("FOO=qoo") }
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
require "spec_helper"
|
||||||
|
require "foreman/engine"
|
||||||
|
require "foreman/export/launchd"
|
||||||
|
require "tmpdir"
|
||||||
|
|
||||||
|
describe Foreman::Export::Launchd, :fakefs do
|
||||||
|
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||||
|
let(:engine) { Foreman::Engine.new(procfile) }
|
||||||
|
let(:options) { Hash.new }
|
||||||
|
let(:launchd) { Foreman::Export::Launchd.new("/tmp/init", engine, options) }
|
||||||
|
|
||||||
|
before(:each) { load_export_templates_into_fakefs("launchd") }
|
||||||
|
before(:each) { stub(launchd).say }
|
||||||
|
|
||||||
|
it "exports to the filesystem" do
|
||||||
|
launchd.export
|
||||||
|
|
||||||
|
normalize_space(File.read("/tmp/init/app-alpha-1.plist")).should == normalize_space(example_export_file("launchd/launchd-a.default"))
|
||||||
|
|
||||||
|
normalize_space(File.read("/tmp/init/app-bravo-1.plist")).should == normalize_space(example_export_file("launchd/launchd-b.default"))
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -33,6 +33,12 @@ describe Foreman::Export::Upstart, :fakefs do
|
|||||||
upstart.export
|
upstart.export
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "quotes and escapes environment variables" do
|
||||||
|
engine.environment['KEY'] = 'd"\|d'
|
||||||
|
upstart.export
|
||||||
|
File.read("/tmp/init/app-alpha-1.conf").should include('KEY="d\"\\\\|d"')
|
||||||
|
end
|
||||||
|
|
||||||
context "with concurrency" do
|
context "with concurrency" do
|
||||||
let(:options) { Hash[:concurrency => "alpha=2"] }
|
let(:options) { Hash[:concurrency => "alpha=2"] }
|
||||||
|
|
||||||
|
|||||||
@@ -121,11 +121,25 @@ describe Foreman::Process do
|
|||||||
output.should include('777')
|
output.should include('777')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should handle arguments' do
|
it 'should handle multi-word arguments (old test)' do
|
||||||
pending
|
# TODO: This test used to be marked pending; it now passes,
|
||||||
|
# but is very slow. The next test is a fast replacement.
|
||||||
run %{ sh -c "trap '' TERM; sleep 10" }
|
run %{ sh -c "trap '' TERM; sleep 10" }
|
||||||
subject.should be_alive
|
subject.should be_alive
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should handle multi-word arguments' do
|
||||||
|
# We have to be a little clever here since Foreman will always
|
||||||
|
# print a status message that includes the command.
|
||||||
|
run %{ sh -c 'echo abcdef | tr a-c x | tr d-f y' }
|
||||||
|
output.should include('xxxyyy')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not clobber "$x"-subexpressions' do
|
||||||
|
pending 'this conflicts with the variable interpolation hack'
|
||||||
|
run %{ sh -c 'echo \$abcdef | tr \$ %' }
|
||||||
|
output.should include('%abcdef')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5000"}
|
process.environment = {"PORT" => "5000"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||||
|
process.stop_grace_time = 45.seconds
|
||||||
|
|
||||||
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5001"}
|
process.environment = {"PORT" => "5001"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||||
|
process.stop_grace_time = 45.seconds
|
||||||
|
|
||||||
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
|
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5000"}
|
process.environment = {"PORT" => "5000"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||||
|
process.stop_grace_time = 45.seconds
|
||||||
|
|
||||||
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5100"}
|
process.environment = {"PORT" => "5100"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||||
|
process.stop_grace_time = 45.seconds
|
||||||
|
|
||||||
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
|
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>app-alpha-1</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>./alpha</string>
|
||||||
|
</array>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/var/log/app/app-alpha-1.log</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>/tmp/app</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>app-bravo-1</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>./bravo</string>
|
||||||
|
</array>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/var/log/app/app-bravo-1.log</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>/tmp/app</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Reference in New Issue
Block a user