ip-wrangler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.hound.yml +3 -0
- data/.rubocop.yml +9 -0
- data/CHANGELOG +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +22 -0
- data/MANUAL.md +14 -0
- data/README.md +182 -0
- data/Rakefile +1 -0
- data/bin/ip-wrangler-clean +6 -0
- data/bin/ip-wrangler-clean.sh +18 -0
- data/bin/ip-wrangler-configure +6 -0
- data/bin/ip-wrangler-configure.sh +78 -0
- data/bin/ip-wrangler-start +6 -0
- data/bin/ip-wrangler-start.sh +71 -0
- data/bin/ip-wrangler-stop +6 -0
- data/bin/ip-wrangler-stop.sh +36 -0
- data/bin/ip-wrangler-test +6 -0
- data/bin/ip-wrangler-test.sh +166 -0
- data/ip-wrangler.gemspec +31 -0
- data/lib/config.ru +127 -0
- data/lib/config.yml.example +20 -0
- data/lib/ip_wrangler/db.rb +99 -0
- data/lib/ip_wrangler/exec.rb +17 -0
- data/lib/ip_wrangler/ip.rb +37 -0
- data/lib/ip_wrangler/iptables.rb +222 -0
- data/lib/ip_wrangler/main.rb +324 -0
- data/lib/ip_wrangler/nat.rb +130 -0
- data/lib/ip_wrangler/version.rb +3 -0
- data/support/initd.md +22 -0
- data/support/initd/ip-wrangler +53 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 89f402bf64890c36deefd8756a91dcf31d077205
|
4
|
+
data.tar.gz: 48b32a9b15c002138927e8b345bf52d471c95486
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a0036134eb6ac45cfb8bfcb1daf2dfcdefb934d199cee57c3658e525793daafe4c220dc1ce7fb9cd1831757b5f8b5fbf899e8b974b2cb229f8d2d57a6a39d4b
|
7
|
+
data.tar.gz: 0d50e1f7dd64c0c948b030d53ee2df14a2dfd3dc40e3d7f2985f5c3c1e1d7f4decee390309a3ca6f4657d61b5f6c3562e571998d578c1abcb03d2d7254bddc50
|
data/.gitignore
ADDED
data/.hound.yml
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ip-wrangler (0.1.0)
|
5
|
+
json (~> 1.8)
|
6
|
+
sequel (~> 4.19)
|
7
|
+
sinatra (~> 1.4)
|
8
|
+
sqlite3 (~> 1.3)
|
9
|
+
thin (~> 1.6)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
daemons (1.1.9)
|
15
|
+
eventmachine (1.0.6)
|
16
|
+
json (1.8.2)
|
17
|
+
rack (1.6.0)
|
18
|
+
rack-protection (1.5.3)
|
19
|
+
rack
|
20
|
+
rake (10.4.2)
|
21
|
+
sequel (4.19.0)
|
22
|
+
sinatra (1.4.5)
|
23
|
+
rack (~> 1.4)
|
24
|
+
rack-protection (~> 1.4)
|
25
|
+
tilt (~> 1.3, >= 1.3.4)
|
26
|
+
sqlite3 (1.3.10)
|
27
|
+
thin (1.6.3)
|
28
|
+
daemons (~> 1.0, >= 1.0.9)
|
29
|
+
eventmachine (~> 1.0)
|
30
|
+
rack (~> 1.0)
|
31
|
+
tilt (1.4.1)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
bundler (~> 1.6)
|
38
|
+
ip-wrangler!
|
39
|
+
rake (~> 10.4)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Paweł Suder
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/MANUAL.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
## Manual installation
|
2
|
+
|
3
|
+
Install this software (as non-root):
|
4
|
+
|
5
|
+
git clone https://github.com/dice-cyfronet/ip-wrangler.git
|
6
|
+
|
7
|
+
Create gem locally (as non-root, inside project directory):
|
8
|
+
|
9
|
+
gem build ip-wrangler.gemspec
|
10
|
+
|
11
|
+
Install created gem:
|
12
|
+
|
13
|
+
gem install ip-wrangler-*.gem
|
14
|
+
|
data/README.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
# IP Wrangler
|
2
|
+
|
3
|
+
[![Code Climate](https://codeclimate.com/github/dice-cyfronet/ip-wrangler/badges/gpa.svg)](https://codeclimate.com/github/dice-cyfronet/ip-wrangler)
|
4
|
+
[![Dependency Status](https://gemnasium.com/dice-cyfronet/ip-wrangler.svg)](https://gemnasium.com/dice-cyfronet/ip-wrangler)
|
5
|
+
|
6
|
+
In polish __Portostawiaczka__
|
7
|
+
|
8
|
+
This application manages DNAT port mappings and IP mappings for Virtual Machines
|
9
|
+
(behind the NAT). It needs to be run on a node which is a router for Virtual
|
10
|
+
Machines. It provides an API reachable via HTTP URL (`GET`, `POST`, `DELETE`)
|
11
|
+
which allows the user to perform changes on `iptables` `nat` tables. It manages
|
12
|
+
a pool of used and empty port mappings or IP mappings using an SQLite database.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
### Requirements
|
17
|
+
|
18
|
+
* `iptables`
|
19
|
+
* `lsof`
|
20
|
+
* `sudo` (the user which runs `ipwrangler` needs permissions to run `/sbin/iptables` and `/usr/bin/lsof` via `sudo`)
|
21
|
+
* `sqlite3` with `libsqlite3-dev`
|
22
|
+
|
23
|
+
### Packages / Dependencies
|
24
|
+
|
25
|
+
Update your system (as root, **optional**):
|
26
|
+
|
27
|
+
aptitude update
|
28
|
+
aptitude upgrade
|
29
|
+
|
30
|
+
Install additional packages (as root, **optional**):
|
31
|
+
|
32
|
+
aptitude install iptables lsof sudo libsqlite3-dev g++ make autoconf bison build-essential libssl-dev libyaml-dev libreadline6 libreadline6-dev zlib1g zlib1g-dev
|
33
|
+
|
34
|
+
Install `ruby` and `bundler` (as root, **optional**):
|
35
|
+
|
36
|
+
mkdir /tmp/ruby
|
37
|
+
pushd /tmp/ruby
|
38
|
+
curl --progress http://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
|
39
|
+
pushd /tmp/ruby/ruby-2.1.2
|
40
|
+
./configure --disable-install-rdoc
|
41
|
+
make
|
42
|
+
make install
|
43
|
+
gem install bundler --no-ri --no-rdoc
|
44
|
+
popd
|
45
|
+
popd
|
46
|
+
|
47
|
+
Install this software:
|
48
|
+
|
49
|
+
gem install ip-wrangler
|
50
|
+
|
51
|
+
Add `user_name` (which will start `ip-wrangler`) to `sudo` group (as root):
|
52
|
+
|
53
|
+
adduser user_name sudo
|
54
|
+
|
55
|
+
To enable `iptables` and `lsof` for user `user_name` modify `/etc/sudoers` (as root)
|
56
|
+
using `visudo`. Add the following line at the bottom of the file:
|
57
|
+
|
58
|
+
user_name host_name= NOPASSWD: /sbin/iptables, /usr/bin/lsof
|
59
|
+
|
60
|
+
`host_name` must be the same like in `/etc/hostname`.
|
61
|
+
|
62
|
+
### Configuration
|
63
|
+
|
64
|
+
Before you start, configure *migratio* installation by executing short wizard:
|
65
|
+
|
66
|
+
ip-wrangler-configure ./config.yml
|
67
|
+
|
68
|
+
You may edit manually configuration file, eg. `config.yml`.
|
69
|
+
|
70
|
+
### Run
|
71
|
+
|
72
|
+
When launching for the first time, run the application in the foreground:
|
73
|
+
|
74
|
+
ip-wrangler-start -c ./config.yml -F
|
75
|
+
|
76
|
+
Verify that everything is okay.
|
77
|
+
|
78
|
+
Application can be run in the background:
|
79
|
+
|
80
|
+
ip-wrangler-start -c ./config.yml -P ./ip-wrangler.pid
|
81
|
+
|
82
|
+
To stop `ipwrangler` which runs in the background:
|
83
|
+
|
84
|
+
ip-wrangler-stop -P ./ip-wrangler.pid
|
85
|
+
|
86
|
+
To clean rules created by `ipwrangler` in `iptables`:
|
87
|
+
|
88
|
+
ip-wrangler-clean <iptables_chain_name|maybe:IPT_WR>
|
89
|
+
|
90
|
+
You can use *init.d* scripts to start and stop *migratio* automatic. Check [`initd.md`](support/initd.md)
|
91
|
+
|
92
|
+
### Log'n'roll
|
93
|
+
|
94
|
+
Use *logrotate* to roll generated logs. Example configuration for *logrotate*:
|
95
|
+
|
96
|
+
# ip-wrangler logrotate settings
|
97
|
+
# based on: http://stackoverflow.com/a/4883967
|
98
|
+
|
99
|
+
/path/to/ip-wrangler/src/log/*.log {
|
100
|
+
daily
|
101
|
+
missingok
|
102
|
+
rotate 90
|
103
|
+
compress
|
104
|
+
notifempty
|
105
|
+
copytruncate
|
106
|
+
}
|
107
|
+
|
108
|
+
## API
|
109
|
+
|
110
|
+
### Port
|
111
|
+
|
112
|
+
Listing:
|
113
|
+
|
114
|
+
* `GET /nat/port` - list all NAT port(s)
|
115
|
+
* `GET /nat/port/<private_ip>` - list NAT port(s) for specified private IP
|
116
|
+
|
117
|
+
Creating:
|
118
|
+
|
119
|
+
* `POST /nat/port/<private_ip>/<private_port>/<protocol>` - create NAT port for specified IP
|
120
|
+
* `POST /nat/port/<private_ip>/<private_port>` - create NAT ports (tcp,udp) for specified IP
|
121
|
+
|
122
|
+
Deleting:
|
123
|
+
|
124
|
+
* `DELETE /nat/port/<private_ip>/<private_port>/<protocol>` - delete NAT port with specified protocol for specified private IP
|
125
|
+
* `DELETE /nat/port/<private_ip>/<private_port` - delete NAT port for specified IP
|
126
|
+
* `DELETE /nat/port/<private_ip>` - delete any NAT port for specified IP
|
127
|
+
|
128
|
+
### IP
|
129
|
+
|
130
|
+
Listing:
|
131
|
+
|
132
|
+
* `GET /nat/ip` - get list of all NAT IPs
|
133
|
+
* `GET /nat/ip/<private_ip>` - get list of NAT IPs for specified private IP
|
134
|
+
|
135
|
+
Creating:
|
136
|
+
|
137
|
+
* `POST /nat/ip/<private_ip>` - create NAT IP for specified private IP
|
138
|
+
|
139
|
+
Deleting:
|
140
|
+
|
141
|
+
* `DELETE /nat/ip/<private_ip>/<public_ip>` - delete NAT IP for specified private IP
|
142
|
+
* `DELETE /nat/ip/<private_ip>` - delete any NAT IP for specified private IP
|
143
|
+
|
144
|
+
## API (old version)
|
145
|
+
|
146
|
+
Listing:
|
147
|
+
|
148
|
+
* `GET /` - get information about REST service
|
149
|
+
* `GET /dnat` - list all NAT port(s)
|
150
|
+
* `GET /dnat/<private_ip>` - list NAT port(s) for specified private IP
|
151
|
+
|
152
|
+
Creating:
|
153
|
+
|
154
|
+
* `POST /dnat/<private_ip>` - create NAT port for specified IP. The request body should be specified in the following format:
|
155
|
+
|
156
|
+
_example_
|
157
|
+
|
158
|
+
[
|
159
|
+
{
|
160
|
+
"port": 21,
|
161
|
+
"proto": tcp
|
162
|
+
},
|
163
|
+
{
|
164
|
+
"port": 22,
|
165
|
+
"proto": udp
|
166
|
+
}
|
167
|
+
]
|
168
|
+
|
169
|
+
Deleting:
|
170
|
+
|
171
|
+
* `DELETE /dnat/<private_ip>/<private_port>/<protocol>` - delete NAT port with specified protocol for specified private IP
|
172
|
+
* `DELETE /dnat/<private_ip>/<private_port>` - delete NAT port for specified IP
|
173
|
+
* `DELETE /dnat/<private_ip>` - delete any NAT port for specified IP
|
174
|
+
|
175
|
+
## Contributing
|
176
|
+
|
177
|
+
1. Fork it!
|
178
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
179
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
180
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
181
|
+
5. Create a new *Pull Request*
|
182
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
if [ -z "$1" ]
|
4
|
+
then
|
5
|
+
echo "Usage: $(basename $0) <iptables_chain_name|maybe:IPT_WR>"
|
6
|
+
exit 1
|
7
|
+
fi
|
8
|
+
|
9
|
+
iptables_chain_name=$1
|
10
|
+
|
11
|
+
set -x
|
12
|
+
|
13
|
+
sudo /sbin/iptables -t nat --delete PREROUTING --jump ${iptables_chain_name}_PRE
|
14
|
+
sudo /sbin/iptables -t nat --delete POSTROUTING --jump ${iptables_chain_name}_POST
|
15
|
+
sudo /sbin/iptables -t nat --flush ${iptables_chain_name}_PRE
|
16
|
+
sudo /sbin/iptables -t nat --flush ${iptables_chain_name}_POST
|
17
|
+
sudo /sbin/iptables -t nat --delete-chain ${iptables_chain_name}_PRE
|
18
|
+
sudo /sbin/iptables -t nat --delete-chain ${iptables_chain_name}_POST
|
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
export __dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
4
|
+
export __dir="$(dirname ${__dir})"
|
5
|
+
|
6
|
+
if [ -z "$1" ]
|
7
|
+
then
|
8
|
+
echo "Usage: $(basename $0) <path_to_config_file:maybe:config.yml>"
|
9
|
+
exit 1
|
10
|
+
fi
|
11
|
+
|
12
|
+
path_to_config_file=$1
|
13
|
+
|
14
|
+
echo "For more information, check: https://github.com/dice-cyfronet/ip-wrangler#configuration"
|
15
|
+
|
16
|
+
echo "====="
|
17
|
+
|
18
|
+
echo "Would like to override your current config with the default settings?"
|
19
|
+
cp -i ${__dir}/lib/config.yml.example ${path_to_config_file}
|
20
|
+
|
21
|
+
__log_dir="$(cat ${path_to_config_file} | grep log_dir: | awk '{print $2}')"
|
22
|
+
__db_path="$(cat ${path_to_config_file} | grep db_path: | awk '{print $2}')"
|
23
|
+
__username="$(cat ${path_to_config_file} | grep username: | awk '{print $2}')"
|
24
|
+
__password="$(cat ${path_to_config_file} | grep password: | awk '{print $2}')"
|
25
|
+
__iptables_chain_name="$(cat ${path_to_config_file} | grep iptables_chain_name: | awk '{print $2}')"
|
26
|
+
__ext_ip="$(cat ${path_to_config_file} | grep ext_ip: | awk '{print $2}')"
|
27
|
+
__port_ip="$(cat ${path_to_config_file} | grep port_ip: | awk '{print $2}')"
|
28
|
+
__port_start="$(cat ${path_to_config_file} | grep port_start: | awk '{print $2}')"
|
29
|
+
__port_stop="$(cat ${path_to_config_file} | grep port_stop: | awk '{print $2}')"
|
30
|
+
|
31
|
+
echo "====="
|
32
|
+
|
33
|
+
echo "Log directory (current value: \"${__log_dir}\", leave empty to use the same)"
|
34
|
+
read __new_log_dir
|
35
|
+
echo "Path to database file (current value: \"${__db_path}\", leave empty to use the same)"
|
36
|
+
read __new_db_path
|
37
|
+
|
38
|
+
echo "HTTP Username (current value: \"${__username}\", leave empty to use the same)"
|
39
|
+
read __new_username
|
40
|
+
echo "HTTP Password (current value: \"${__password}\", leave empty to use the same)"
|
41
|
+
read __new_password
|
42
|
+
|
43
|
+
echo "Iptables chains prefix (current value: \"${__iptables_chain_name}\", leave empty to use the same)"
|
44
|
+
read __new_iptables_chain_name
|
45
|
+
|
46
|
+
echo "External IP address user for NAT port. If your server is indicated by a different address than assigned to the outside interface, enter it here. (current value: \"${__ext_ip}\", leave empty to use the same)"
|
47
|
+
read __new_ext_ip
|
48
|
+
echo "Public IP address used for NAT port. Enter address which is assigned to the outside interface. (current value: \"${__port_ip}\", leave empty to use the same)"
|
49
|
+
read __new_port_ip
|
50
|
+
echo "First port in range of available ports for NAT (current value: \"${__port_start}\")"
|
51
|
+
read __new_port_start
|
52
|
+
echo "Last port in range of available ports for NAT (current value: \"${__port_stop}\")"
|
53
|
+
read __new_port_stop
|
54
|
+
|
55
|
+
echo "To update list of public IP used for NAT IP use your favorite text editor to edit \`${path_to_config_file}\`"
|
56
|
+
|
57
|
+
function check_and_replace() {
|
58
|
+
__new_value="__new_${1}"
|
59
|
+
__value="__${1}"
|
60
|
+
if [ ! -z "${!__new_value}" ] && [ "${!__value}" != "${!__new_value}" ]; then
|
61
|
+
sed -i "s#${1}:.*#${1}: ${!__new_value}#g" ${path_to_config_file}
|
62
|
+
fi
|
63
|
+
}
|
64
|
+
|
65
|
+
check_and_replace log_dir
|
66
|
+
check_and_replace db_path
|
67
|
+
check_and_replace username
|
68
|
+
check_and_replace password
|
69
|
+
check_and_replace iptables_chain_name
|
70
|
+
check_and_replace ext_ip
|
71
|
+
check_and_replace port_ip
|
72
|
+
check_and_replace port_start
|
73
|
+
check_and_replace port_stop
|
74
|
+
|
75
|
+
echo "====="
|
76
|
+
echo "Show ${path_to_config_file}. You may edit this file to add IP which will use to IP mapping."
|
77
|
+
echo "-----"
|
78
|
+
cat ${path_to_config_file}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
export __dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
4
|
+
export __dir=$( dirname ${__dir} )
|
5
|
+
|
6
|
+
export __port=8400
|
7
|
+
export __ip=0.0.0.0
|
8
|
+
export __tag=IpWrangler
|
9
|
+
export __daemon=-d
|
10
|
+
|
11
|
+
function usage() {
|
12
|
+
echo "Usage: $(basename $0) -c <config_file|maybe:config.yml> -P <pid_file|maybe:ip-wrangler.pid> -i <ip|default:0.0.0.0> -p <port|default:8400> -t <tag|default:IpWrangler> -F (foreground mode) -h (help and exit)"
|
13
|
+
}
|
14
|
+
|
15
|
+
while getopts 'c:P:i:p:t:Fh' __flag; do
|
16
|
+
case "${__flag}" in
|
17
|
+
c)
|
18
|
+
export __config_file="$(realpath ${OPTARG})"
|
19
|
+
;;
|
20
|
+
P)
|
21
|
+
touch ${OPTARG}
|
22
|
+
export __pid_file="$(realpath ${OPTARG})"
|
23
|
+
rm ${OPTARG}
|
24
|
+
;;
|
25
|
+
i)
|
26
|
+
export __ip=${OPTARG}
|
27
|
+
;;
|
28
|
+
p)
|
29
|
+
export __port=${OPTARG}
|
30
|
+
;;
|
31
|
+
t)
|
32
|
+
export __tag=${OPTARG}
|
33
|
+
;;
|
34
|
+
F)
|
35
|
+
export __daemon=
|
36
|
+
export __no_log=1
|
37
|
+
;;
|
38
|
+
h)
|
39
|
+
usage
|
40
|
+
exit 0
|
41
|
+
;;
|
42
|
+
*)
|
43
|
+
error "Unexpected option: ${__flag}"
|
44
|
+
usage
|
45
|
+
exit 1
|
46
|
+
;;
|
47
|
+
esac
|
48
|
+
done
|
49
|
+
|
50
|
+
if [ ! -z "${__daemon}" ] && [ -z "${__pid_file}" ]
|
51
|
+
then
|
52
|
+
echo "No PID file defined in daemon mode."
|
53
|
+
usage
|
54
|
+
exit 1
|
55
|
+
fi
|
56
|
+
|
57
|
+
if [ ! -z "${__pid_file}" ]
|
58
|
+
then
|
59
|
+
__pid_option="-P ${__pid_file}"
|
60
|
+
fi
|
61
|
+
|
62
|
+
if [ -z "${__config_file}" ]
|
63
|
+
then
|
64
|
+
echo "No config file defined."
|
65
|
+
usage
|
66
|
+
exit 1
|
67
|
+
fi
|
68
|
+
|
69
|
+
pushd ${__dir}/lib/ 2>&1 >> /dev/null
|
70
|
+
thin ${__daemon} ${__pid_option} -a ${__ip} -p ${__port} -R ./config.ru --tag ${__tag} start
|
71
|
+
popd 2>&1 >> /dev/null
|