tayo 0.2.3 โ 0.3.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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +38 -79
- data/lib/tayo/cli.rb +0 -6
- data/lib/tayo/commands/cf.rb +137 -30
- data/lib/tayo/commands/gh.rb +90 -55
- data/lib/tayo/commands/init.rb +15 -2
- data/lib/tayo/version.rb +1 -1
- metadata +2 -10
- data/lib/tayo/commands/proxy.rb +0 -97
- data/lib/tayo/proxy/cloudflare_client.rb +0 -323
- data/lib/tayo/proxy/docker_manager.rb +0 -150
- data/lib/tayo/proxy/network_config.rb +0 -147
- data/lib/tayo/proxy/traefik_config.rb +0 -303
- data/lib/tayo/proxy/welcome_service.rb +0 -337
- data/lib/templates/welcome/Dockerfile +0 -14
- data/lib/templates/welcome/index.html +0 -173
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "colorize"
|
|
4
|
-
require "tty-prompt"
|
|
5
|
-
require "net/http"
|
|
6
|
-
require "json"
|
|
7
|
-
require "uri"
|
|
8
|
-
require "fileutils"
|
|
9
|
-
|
|
10
|
-
module Tayo
|
|
11
|
-
module Proxy
|
|
12
|
-
class CloudflareClient
|
|
13
|
-
TOKEN_FILE = File.expand_path("~/.tayo/cloudflare_token")
|
|
14
|
-
|
|
15
|
-
def initialize
|
|
16
|
-
@prompt = TTY::Prompt.new
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def ensure_token
|
|
20
|
-
@token = load_saved_token
|
|
21
|
-
|
|
22
|
-
if @token
|
|
23
|
-
puts "๐ ์ ์ฅ๋ Cloudflare ํ ํฐ์ ํ์ธํฉ๋๋ค...".colorize(:yellow)
|
|
24
|
-
|
|
25
|
-
if test_token(@token)
|
|
26
|
-
puts "โ
์ ์ฅ๋ Cloudflare ํ ํฐ์ด ์ ํจํฉ๋๋ค.".colorize(:green)
|
|
27
|
-
else
|
|
28
|
-
puts "โ ๏ธ ์ ์ฅ๋ ํ ํฐ์ด ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ต๋๋ค.".colorize(:yellow)
|
|
29
|
-
puts "์ ํ ํฐ์ ์
๋ ฅํด์ฃผ์ธ์.".colorize(:cyan)
|
|
30
|
-
open_token_creation_page
|
|
31
|
-
@token = get_cloudflare_token
|
|
32
|
-
save_token(@token)
|
|
33
|
-
end
|
|
34
|
-
else
|
|
35
|
-
puts "\n๐ Cloudflare API ํ ํฐ์ด ํ์ํฉ๋๋ค.".colorize(:yellow)
|
|
36
|
-
open_token_creation_page
|
|
37
|
-
@token = get_cloudflare_token
|
|
38
|
-
save_token(@token)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def select_domains
|
|
43
|
-
puts "\n๐ Cloudflare ๋๋ฉ์ธ ๋ชฉ๋ก์ ์กฐํํฉ๋๋ค...".colorize(:yellow)
|
|
44
|
-
|
|
45
|
-
zones = get_cloudflare_zones
|
|
46
|
-
|
|
47
|
-
if zones.empty?
|
|
48
|
-
puts "โ Cloudflare์ ๋ฑ๋ก๋ ๋๋ฉ์ธ์ด ์์ต๋๋ค.".colorize(:red)
|
|
49
|
-
puts "๋จผ์ https://dash.cloudflare.com ์์ ๋๋ฉ์ธ์ ์ถ๊ฐํด์ฃผ์ธ์.".colorize(:cyan)
|
|
50
|
-
return []
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
zone_choices = zones.map { |zone| "#{zone['name']} (#{zone['status']})" }
|
|
54
|
-
|
|
55
|
-
selected_zones = @prompt.multi_select(
|
|
56
|
-
"Caddy๋ก ๋ผ์ฐํ
ํ ๋๋ฉ์ธ์ ์ ํํ์ธ์ (Space๋ก ์ ํ, Enter๋ก ์๋ฃ):",
|
|
57
|
-
zone_choices,
|
|
58
|
-
per_page: 10,
|
|
59
|
-
min: 1
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
# ์ ํ๋ ํญ๋ชฉ์ด ์์ผ๋ฉด ๋น ๋ฐฐ์ด ๋ฐํ
|
|
63
|
-
return [] if selected_zones.nil? || selected_zones.empty?
|
|
64
|
-
|
|
65
|
-
puts "DEBUG: selected_zones = #{selected_zones.inspect}" if ENV["DEBUG"]
|
|
66
|
-
|
|
67
|
-
selected_domains = selected_zones.map do |selection|
|
|
68
|
-
zone_name = selection.split(' ').first
|
|
69
|
-
zone = zones.find { |z| z['name'] == zone_name }
|
|
70
|
-
|
|
71
|
-
# ์๋ธ๋๋ฉ์ธ ์ถ๊ฐ ์ฌ๋ถ ํ์ธ
|
|
72
|
-
if @prompt.yes?("#{zone_name}์ ์๋ธ๋๋ฉ์ธ์ ์ถ๊ฐํ์๊ฒ ์ต๋๊น?")
|
|
73
|
-
subdomain = @prompt.ask("์๋ธ๋๋ฉ์ธ์ ์
๋ ฅํ์ธ์ (์: app, api):")
|
|
74
|
-
if subdomain && !subdomain.empty?
|
|
75
|
-
{
|
|
76
|
-
domain: "#{subdomain}.#{zone_name}",
|
|
77
|
-
zone_id: zone['id'],
|
|
78
|
-
zone_name: zone_name
|
|
79
|
-
}
|
|
80
|
-
else
|
|
81
|
-
{
|
|
82
|
-
domain: zone_name,
|
|
83
|
-
zone_id: zone['id'],
|
|
84
|
-
zone_name: zone_name
|
|
85
|
-
}
|
|
86
|
-
end
|
|
87
|
-
else
|
|
88
|
-
{
|
|
89
|
-
domain: zone_name,
|
|
90
|
-
zone_id: zone['id'],
|
|
91
|
-
zone_name: zone_name
|
|
92
|
-
}
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
puts "\nโ
์ ํ๋ ๋๋ฉ์ธ:".colorize(:green)
|
|
97
|
-
selected_domains.each do |d|
|
|
98
|
-
puts " โข #{d[:domain]}".colorize(:cyan)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
puts "DEBUG: returning selected_domains = #{selected_domains.inspect}" if ENV["DEBUG"]
|
|
102
|
-
|
|
103
|
-
selected_domains
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def setup_dns_records(domains, public_ip)
|
|
107
|
-
domains.each do |domain_info|
|
|
108
|
-
domain = domain_info[:domain]
|
|
109
|
-
zone_id = domain_info[:zone_id]
|
|
110
|
-
|
|
111
|
-
puts "\nโ๏ธ #{domain}์ DNS ๋ ์ฝ๋๋ฅผ ์ค์ ํฉ๋๋ค...".colorize(:yellow)
|
|
112
|
-
|
|
113
|
-
# ๊ธฐ์กด A ๋ ์ฝ๋ ํ์ธ
|
|
114
|
-
existing_records = get_dns_records(zone_id, domain, ['A'])
|
|
115
|
-
|
|
116
|
-
if existing_records.any?
|
|
117
|
-
existing_record = existing_records.first
|
|
118
|
-
if existing_record['content'] == public_ip
|
|
119
|
-
puts "โ
#{domain} โ #{public_ip} (์ด๋ฏธ ์ค์ ๋จ)".colorize(:green)
|
|
120
|
-
else
|
|
121
|
-
puts "โ ๏ธ ๊ธฐ์กด ๋ ์ฝ๋๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.".colorize(:yellow)
|
|
122
|
-
update_dns_record(zone_id, existing_record['id'], public_ip)
|
|
123
|
-
end
|
|
124
|
-
else
|
|
125
|
-
# ์ A ๋ ์ฝ๋ ์์ฑ
|
|
126
|
-
create_dns_record(zone_id, domain, 'A', public_ip)
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
private
|
|
132
|
-
|
|
133
|
-
def load_saved_token
|
|
134
|
-
return nil unless File.exist?(TOKEN_FILE)
|
|
135
|
-
File.read(TOKEN_FILE).strip
|
|
136
|
-
rescue
|
|
137
|
-
nil
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def save_token(token)
|
|
141
|
-
dir = File.dirname(TOKEN_FILE)
|
|
142
|
-
|
|
143
|
-
# ๋๋ ํ ๋ฆฌ๊ฐ ํ์ผ๋ก ์กด์ฌํ๋ ๊ฒฝ์ฐ ์ญ์
|
|
144
|
-
if File.exist?(dir) && !File.directory?(dir)
|
|
145
|
-
FileUtils.rm(dir)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# ๋๋ ํ ๋ฆฌ๊ฐ ์์ ๋๋ง ์์ฑ
|
|
149
|
-
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
150
|
-
|
|
151
|
-
File.write(TOKEN_FILE, token)
|
|
152
|
-
File.chmod(0600, TOKEN_FILE)
|
|
153
|
-
puts "โ
API ํ ํฐ์ด ์ ์ฅ๋์์ต๋๋ค.".colorize(:green)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def open_token_creation_page
|
|
157
|
-
puts "ํ ํฐ ์์ฑ ํ์ด์ง๋ฅผ ์ฝ๋๋ค...".colorize(:cyan)
|
|
158
|
-
system("open 'https://dash.cloudflare.com/profile/api-tokens' 2>/dev/null || xdg-open 'https://dash.cloudflare.com/profile/api-tokens' 2>/dev/null")
|
|
159
|
-
|
|
160
|
-
puts "\n๋ค์ ๊ถํ์ผ๋ก ํ ํฐ์ ์์ฑํด์ฃผ์ธ์:".colorize(:yellow)
|
|
161
|
-
puts ""
|
|
162
|
-
puts "ํ๊ตญ์ด ํ๋ฉด:".colorize(:gray)
|
|
163
|
-
puts "โข ์์ญ โ ์์ญ โ ์ฝ๊ธฐ".colorize(:white)
|
|
164
|
-
puts "โข ์์ญ โ DNS โ ํธ์ง".colorize(:white)
|
|
165
|
-
puts " (์์ญ ๋ฆฌ์์ค๋ '๋ชจ๋ ์์ญ' ์ ํ)".colorize(:gray)
|
|
166
|
-
puts ""
|
|
167
|
-
puts "English:".colorize(:gray)
|
|
168
|
-
puts "โข Zone โ Zone โ Read".colorize(:white)
|
|
169
|
-
puts "โข Zone โ DNS โ Edit".colorize(:white)
|
|
170
|
-
puts " (Zone Resources: Select 'All zones')".colorize(:gray)
|
|
171
|
-
puts ""
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def get_cloudflare_token
|
|
175
|
-
token = @prompt.mask("์์ฑ๋ Cloudflare API ํ ํฐ์ ๋ถ์ฌ๋ฃ์ผ์ธ์:")
|
|
176
|
-
|
|
177
|
-
if token.nil? || token.strip.empty?
|
|
178
|
-
puts "โ ํ ํฐ์ด ์
๋ ฅ๋์ง ์์์ต๋๋ค.".colorize(:red)
|
|
179
|
-
exit 1
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
if test_token(token.strip)
|
|
183
|
-
puts "โ
ํ ํฐ์ด ํ์ธ๋์์ต๋๋ค.".colorize(:green)
|
|
184
|
-
return token.strip
|
|
185
|
-
else
|
|
186
|
-
puts "โ ํ ํฐ์ด ์ฌ๋ฐ๋ฅด์ง ์๊ฑฐ๋ ๊ถํ์ด ๋ถ์กฑํฉ๋๋ค.".colorize(:red)
|
|
187
|
-
exit 1
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def test_token(token)
|
|
192
|
-
uri = URI('https://api.cloudflare.com/client/v4/user/tokens/verify')
|
|
193
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
194
|
-
http.use_ssl = true
|
|
195
|
-
|
|
196
|
-
request = Net::HTTP::Get.new(uri)
|
|
197
|
-
request['Authorization'] = "Bearer #{token}"
|
|
198
|
-
request['Content-Type'] = 'application/json'
|
|
199
|
-
|
|
200
|
-
response = http.request(request)
|
|
201
|
-
|
|
202
|
-
if response.code == '200'
|
|
203
|
-
data = JSON.parse(response.body)
|
|
204
|
-
return data['success'] == true
|
|
205
|
-
else
|
|
206
|
-
return false
|
|
207
|
-
end
|
|
208
|
-
rescue => e
|
|
209
|
-
puts "โ ๏ธ ํ ํฐ ๊ฒ์ฆ ์ค ์ค๋ฅ: #{e.message}".colorize(:yellow) if ENV["DEBUG"]
|
|
210
|
-
false
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def get_cloudflare_zones
|
|
214
|
-
uri = URI('https://api.cloudflare.com/client/v4/zones')
|
|
215
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
216
|
-
http.use_ssl = true
|
|
217
|
-
|
|
218
|
-
request = Net::HTTP::Get.new(uri)
|
|
219
|
-
request['Authorization'] = "Bearer #{@token}"
|
|
220
|
-
request['Content-Type'] = 'application/json'
|
|
221
|
-
|
|
222
|
-
response = http.request(request)
|
|
223
|
-
|
|
224
|
-
if response.code == '200'
|
|
225
|
-
data = JSON.parse(response.body)
|
|
226
|
-
return data['result'] || []
|
|
227
|
-
else
|
|
228
|
-
puts "โ ๋๋ฉ์ธ ๋ชฉ๋ก ์กฐํ์ ์คํจํ์ต๋๋ค: #{response.code}".colorize(:red)
|
|
229
|
-
[]
|
|
230
|
-
end
|
|
231
|
-
rescue => e
|
|
232
|
-
puts "โ API ์์ฒญ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: #{e.message}".colorize(:red)
|
|
233
|
-
[]
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def get_dns_records(zone_id, name, types)
|
|
237
|
-
records = []
|
|
238
|
-
|
|
239
|
-
types.each do |type|
|
|
240
|
-
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
|
241
|
-
uri.query = URI.encode_www_form({
|
|
242
|
-
type: type,
|
|
243
|
-
name: name
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
247
|
-
http.use_ssl = true
|
|
248
|
-
|
|
249
|
-
request = Net::HTTP::Get.new(uri)
|
|
250
|
-
request['Authorization'] = "Bearer #{@token}"
|
|
251
|
-
request['Content-Type'] = 'application/json'
|
|
252
|
-
|
|
253
|
-
response = http.request(request)
|
|
254
|
-
|
|
255
|
-
if response.code == '200'
|
|
256
|
-
data = JSON.parse(response.body)
|
|
257
|
-
records.concat(data['result'] || [])
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
records
|
|
262
|
-
rescue => e
|
|
263
|
-
puts "โ ๏ธ DNS ๋ ์ฝ๋ ์กฐํ ์ค ์ค๋ฅ: #{e.message}".colorize(:yellow)
|
|
264
|
-
[]
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def create_dns_record(zone_id, name, type, content)
|
|
268
|
-
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
|
269
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
270
|
-
http.use_ssl = true
|
|
271
|
-
|
|
272
|
-
request = Net::HTTP::Post.new(uri)
|
|
273
|
-
request['Authorization'] = "Bearer #{@token}"
|
|
274
|
-
request['Content-Type'] = 'application/json'
|
|
275
|
-
|
|
276
|
-
data = {
|
|
277
|
-
type: type,
|
|
278
|
-
name: name,
|
|
279
|
-
content: content,
|
|
280
|
-
ttl: 300,
|
|
281
|
-
proxied: false
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
request.body = data.to_json
|
|
285
|
-
response = http.request(request)
|
|
286
|
-
|
|
287
|
-
if response.code == '200'
|
|
288
|
-
puts "โ
#{name} โ #{content} (A ๋ ์ฝ๋ ์์ฑ๋จ)".colorize(:green)
|
|
289
|
-
else
|
|
290
|
-
puts "โ DNS ๋ ์ฝ๋ ์์ฑ ์คํจ: #{response.code}".colorize(:red)
|
|
291
|
-
puts response.body if ENV["DEBUG"]
|
|
292
|
-
end
|
|
293
|
-
rescue => e
|
|
294
|
-
puts "โ DNS ๋ ์ฝ๋ ์์ฑ ์ค ์ค๋ฅ: #{e.message}".colorize(:red)
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
def update_dns_record(zone_id, record_id, new_content)
|
|
298
|
-
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records/#{record_id}")
|
|
299
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
300
|
-
http.use_ssl = true
|
|
301
|
-
|
|
302
|
-
request = Net::HTTP::Patch.new(uri)
|
|
303
|
-
request['Authorization'] = "Bearer #{@token}"
|
|
304
|
-
request['Content-Type'] = 'application/json'
|
|
305
|
-
|
|
306
|
-
data = {
|
|
307
|
-
content: new_content
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
request.body = data.to_json
|
|
311
|
-
response = http.request(request)
|
|
312
|
-
|
|
313
|
-
if response.code == '200'
|
|
314
|
-
puts "โ
DNS ๋ ์ฝ๋๊ฐ ์
๋ฐ์ดํธ๋์์ต๋๋ค.".colorize(:green)
|
|
315
|
-
else
|
|
316
|
-
puts "โ DNS ๋ ์ฝ๋ ์
๋ฐ์ดํธ ์คํจ: #{response.code}".colorize(:red)
|
|
317
|
-
end
|
|
318
|
-
rescue => e
|
|
319
|
-
puts "โ DNS ๋ ์ฝ๋ ์
๋ฐ์ดํธ ์ค ์ค๋ฅ: #{e.message}".colorize(:red)
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
end
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "colorize"
|
|
4
|
-
require "json"
|
|
5
|
-
|
|
6
|
-
module Tayo
|
|
7
|
-
module Proxy
|
|
8
|
-
class DockerManager
|
|
9
|
-
def check_containers
|
|
10
|
-
puts "\n๐ณ Docker ์ปจํ
์ด๋ ์ํ๋ฅผ ํ์ธํฉ๋๋ค...".colorize(:yellow)
|
|
11
|
-
|
|
12
|
-
unless docker_installed?
|
|
13
|
-
puts "โ Docker๊ฐ ์ค์น๋์ด ์์ง ์์ต๋๋ค.".colorize(:red)
|
|
14
|
-
puts "https://www.docker.com/get-started ์์ Docker๋ฅผ ์ค์นํด์ฃผ์ธ์.".colorize(:cyan)
|
|
15
|
-
exit 1
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
unless docker_running?
|
|
19
|
-
puts "โ Docker๊ฐ ์คํ๋๊ณ ์์ง ์์ต๋๋ค.".colorize(:red)
|
|
20
|
-
puts "Docker Desktop์ ์คํํด์ฃผ์ธ์.".colorize(:cyan)
|
|
21
|
-
exit 1
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Kamal Proxy ์ํ ํ์ธ
|
|
25
|
-
check_kamal_proxy_status
|
|
26
|
-
|
|
27
|
-
# Caddy ์ํ ํ์ธ
|
|
28
|
-
check_caddy_status
|
|
29
|
-
|
|
30
|
-
puts ""
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def container_exists?(name)
|
|
34
|
-
output = `docker ps -a --filter "name=^#{name}$" --format "{{.Names}}" 2>/dev/null`.strip
|
|
35
|
-
!output.empty?
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def container_running?(name)
|
|
39
|
-
output = `docker ps --filter "name=^#{name}$" --format "{{.Names}}" 2>/dev/null`.strip
|
|
40
|
-
!output.empty?
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def check_port_binding(container, ports)
|
|
44
|
-
return false unless container_running?(container)
|
|
45
|
-
|
|
46
|
-
ports.all? do |port|
|
|
47
|
-
output = `docker port #{container} #{port} 2>/dev/null`.strip
|
|
48
|
-
!output.empty?
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def port_in_use?(port)
|
|
53
|
-
# Docker ์ปจํ
์ด๋๊ฐ ํฌํธ๋ฅผ ์ฌ์ฉ ์ค์ธ์ง ํ์ธ
|
|
54
|
-
docker_using = `docker ps --format "table {{.Names}}\t{{.Ports}}" | grep -E "0\\.0\\.0\\.0:#{port}->|\\*:#{port}->" 2>/dev/null`.strip
|
|
55
|
-
return true unless docker_using.empty?
|
|
56
|
-
|
|
57
|
-
# ์์คํ
ํฌํธ ํ์ธ
|
|
58
|
-
if RUBY_PLATFORM.include?("darwin")
|
|
59
|
-
# macOS
|
|
60
|
-
output = `lsof -iTCP:#{port} -sTCP:LISTEN 2>/dev/null`.strip
|
|
61
|
-
else
|
|
62
|
-
# Linux
|
|
63
|
-
output = `netstat -tln 2>/dev/null | grep ":#{port}"`.strip
|
|
64
|
-
output = `ss -tln 2>/dev/null | grep ":#{port}"`.strip if output.empty?
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
!output.empty?
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def stop_container(name)
|
|
71
|
-
return unless container_exists?(name)
|
|
72
|
-
|
|
73
|
-
puts "๐ #{name} ์ปจํ
์ด๋๋ฅผ ์ค์งํฉ๋๋ค...".colorize(:yellow)
|
|
74
|
-
system("docker stop #{name} >/dev/null 2>&1")
|
|
75
|
-
system("docker rm #{name} >/dev/null 2>&1")
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def get_container_network(name)
|
|
79
|
-
return nil unless container_running?(name)
|
|
80
|
-
|
|
81
|
-
output = `docker inspect #{name} --format '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}' 2>/dev/null`.strip
|
|
82
|
-
output.empty? ? nil : output
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def create_network_if_not_exists(network_name = "tayo-proxy")
|
|
86
|
-
existing = `docker network ls --filter "name=^#{network_name}$" --format "{{.Name}}" 2>/dev/null`.strip
|
|
87
|
-
|
|
88
|
-
if existing.empty?
|
|
89
|
-
puts "๐ก Docker ๋คํธ์ํฌ '#{network_name}'๋ฅผ ์์ฑํฉ๋๋ค...".colorize(:yellow)
|
|
90
|
-
system("docker network create #{network_name} >/dev/null 2>&1")
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
network_name
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
private
|
|
97
|
-
|
|
98
|
-
def docker_installed?
|
|
99
|
-
system("which docker >/dev/null 2>&1")
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def docker_running?
|
|
103
|
-
system("docker info >/dev/null 2>&1")
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def check_kamal_proxy_status
|
|
107
|
-
# Traefik์ผ๋ก ๊ต์ฒด๋์ด ๋ ์ด์ ์ฌ์ฉํ์ง ์์
|
|
108
|
-
check_traefik_status
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def check_traefik_status
|
|
112
|
-
if container_running?("traefik")
|
|
113
|
-
if check_port_binding("traefik", [80, 443])
|
|
114
|
-
puts "โ
Traefik: ์คํ ์ค (80, 443 ํฌํธ ์ฌ์ฉ)".colorize(:green)
|
|
115
|
-
else
|
|
116
|
-
puts "โ ๏ธ Traefik: ์คํ ์ค์ด์ง๋ง ํฌํธ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋ฐ์ธ๋ฉ๋์ง ์์".colorize(:yellow)
|
|
117
|
-
show_port_conflicts
|
|
118
|
-
end
|
|
119
|
-
elsif container_exists?("traefik")
|
|
120
|
-
puts "โ ๏ธ Traefik: ์ค์ง๋จ".colorize(:yellow)
|
|
121
|
-
else
|
|
122
|
-
puts "โน๏ธ Traefik: ์ค์น๋์ง ์์".colorize(:gray)
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def check_caddy_status
|
|
127
|
-
# Caddy๋ ๋ ์ด์ ์ฌ์ฉํ์ง ์์
|
|
128
|
-
# ์ด ๋ฉ์๋๋ ํ์ ํธํ์ฑ์ ์ํด ์ ์งํ์ง๋ง ์๋ฌด๊ฒ๋ ํ์ง ์์
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def show_port_conflicts
|
|
132
|
-
[80, 443].each do |port|
|
|
133
|
-
if port_in_use?(port)
|
|
134
|
-
# ํฌํธ๋ฅผ ์ฌ์ฉ ์ค์ธ ํ๋ก์ธ์ค ์ฐพ๊ธฐ
|
|
135
|
-
if RUBY_PLATFORM.include?("darwin")
|
|
136
|
-
process = `lsof -iTCP:#{port} -sTCP:LISTEN 2>/dev/null | grep LISTEN | head -1`.strip
|
|
137
|
-
else
|
|
138
|
-
process = `netstat -tlnp 2>/dev/null | grep ":#{port}" | head -1`.strip
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
unless process.empty?
|
|
142
|
-
puts " โ ๏ธ ํฌํธ #{port}๊ฐ ๋ค๋ฅธ ํ๋ก์ธ์ค์์ ์ฌ์ฉ ์ค์
๋๋ค:".colorize(:yellow)
|
|
143
|
-
puts " #{process}".colorize(:gray)
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "colorize"
|
|
4
|
-
require "tty-prompt"
|
|
5
|
-
|
|
6
|
-
module Tayo
|
|
7
|
-
module Proxy
|
|
8
|
-
class NetworkConfig
|
|
9
|
-
attr_reader :public_ip, :internal_ip, :external_http, :external_https
|
|
10
|
-
|
|
11
|
-
def initialize
|
|
12
|
-
@prompt = TTY::Prompt.new
|
|
13
|
-
@use_custom_ports = false
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def detect_ips
|
|
17
|
-
puts "\n๐ ๋คํธ์ํฌ ์ ๋ณด๋ฅผ ํ์ธํฉ๋๋ค...".colorize(:yellow)
|
|
18
|
-
|
|
19
|
-
# ๊ณต์ธ IP ๊ฐ์ง
|
|
20
|
-
print "๊ณต์ธ IP ํ์ธ ์ค... "
|
|
21
|
-
@public_ip = detect_public_ip
|
|
22
|
-
puts "#{@public_ip}".colorize(:green)
|
|
23
|
-
|
|
24
|
-
# ๋ด๋ถ IP ๊ฐ์ง
|
|
25
|
-
print "๋ด๋ถ IP ํ์ธ ์ค... "
|
|
26
|
-
@internal_ip = detect_internal_ip
|
|
27
|
-
puts "#{@internal_ip}".colorize(:green)
|
|
28
|
-
|
|
29
|
-
puts ""
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def configure_ports
|
|
33
|
-
puts "\n๐ ์ธ๋ถ ์ ์ ํฌํธ๋ฅผ ์ค์ ํฉ๋๋ค.".colorize(:yellow)
|
|
34
|
-
puts "Kamal Proxy๋ ํญ์ 80, 443 ํฌํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค.".colorize(:gray)
|
|
35
|
-
puts ""
|
|
36
|
-
|
|
37
|
-
choices = [
|
|
38
|
-
{ name: "๊ณต์ ๊ธฐ์์ 80, 443์ ์ง์ ํฌ์๋ฉ (๊ธฐ๋ณธ)", value: :direct },
|
|
39
|
-
{ name: "๋ค๋ฅธ ํฌํธ๋ฅผ ์ฌ์ฉํ์ฌ ํฌ์๋ฉ (์: 8080โ80, 8443โ443)", value: :custom }
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
choice = @prompt.select("ํฌํธ ์ค์ ๋ฐฉ์์ ์ ํํ์ธ์:", choices)
|
|
43
|
-
|
|
44
|
-
if choice == :custom
|
|
45
|
-
@use_custom_ports = true
|
|
46
|
-
|
|
47
|
-
@external_http = @prompt.ask("HTTP ์ธ๋ถ ํฌํธ (๊ธฐ๋ณธ: 8080):", default: "8080")
|
|
48
|
-
@external_https = @prompt.ask("HTTPS ์ธ๋ถ ํฌํธ (๊ธฐ๋ณธ: 8443):", default: "8443")
|
|
49
|
-
|
|
50
|
-
puts "\nโ
์ธ๋ถ ํฌํธ๊ฐ ์ค์ ๋์์ต๋๋ค:".colorize(:green)
|
|
51
|
-
puts " HTTP: #{@external_http}".colorize(:gray)
|
|
52
|
-
puts " HTTPS: #{@external_https}".colorize(:gray)
|
|
53
|
-
else
|
|
54
|
-
@external_http = "80"
|
|
55
|
-
@external_https = "443"
|
|
56
|
-
|
|
57
|
-
puts "\nโ
ํ์ค ํฌํธ(80, 443)๋ฅผ ์ฌ์ฉํฉ๋๋ค.".colorize(:green)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def use_custom_ports?
|
|
62
|
-
@use_custom_ports
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def show_port_forwarding_guide
|
|
66
|
-
return unless use_custom_ports?
|
|
67
|
-
|
|
68
|
-
puts "\n๐ก ๊ณต์ ๊ธฐ ํฌํธํฌ์๋ฉ ์ค์ ์๋ด:".colorize(:yellow)
|
|
69
|
-
puts "โ" * 50
|
|
70
|
-
puts "์ธ๋ถ ํฌํธ #{@external_http} โ #{@internal_ip}:80".colorize(:white)
|
|
71
|
-
puts "์ธ๋ถ ํฌํธ #{@external_https} โ #{@internal_ip}:443".colorize(:white)
|
|
72
|
-
puts "โ" * 50
|
|
73
|
-
puts ""
|
|
74
|
-
puts "์ ์ค์ ์ ๊ณต์ ๊ธฐ ๊ด๋ฆฌ ํ์ด์ง์์ ์๋ฃํด์ฃผ์ธ์.".colorize(:cyan)
|
|
75
|
-
puts "์ผ๋ฐ์ ์ธ ์ ์ ์ฃผ์: http://192.168.1.1".colorize(:gray)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
def detect_public_ip
|
|
81
|
-
# curl์ ์ฌ์ฉํ ๊ณต์ธ IP ๊ฐ์ง
|
|
82
|
-
ip = `curl -s ifconfig.me 2>/dev/null`.strip
|
|
83
|
-
|
|
84
|
-
# ๋์ฒด ๋ฐฉ๋ฒ๋ค
|
|
85
|
-
if ip.empty? || !valid_ip?(ip)
|
|
86
|
-
ip = `curl -s ipecho.net/plain 2>/dev/null`.strip
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
if ip.empty? || !valid_ip?(ip)
|
|
90
|
-
ip = `curl -s icanhazip.com 2>/dev/null`.strip
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if ip.empty? || !valid_ip?(ip)
|
|
94
|
-
puts "\nโ ๏ธ ๊ณต์ธ IP๋ฅผ ์๋์ผ๋ก ๊ฐ์งํ ์ ์์ต๋๋ค.".colorize(:yellow)
|
|
95
|
-
ip = @prompt.ask("๊ณต์ธ IP๋ฅผ ์ง์ ์
๋ ฅํด์ฃผ์ธ์:") do |q|
|
|
96
|
-
q.validate(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/, "์ฌ๋ฐ๋ฅธ IP ํ์์ ์
๋ ฅํด์ฃผ์ธ์")
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
ip
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def detect_internal_ip
|
|
104
|
-
# macOS
|
|
105
|
-
if RUBY_PLATFORM.include?("darwin")
|
|
106
|
-
# en0 (Wi-Fi) ๋๋ en1 (Ethernet) ์ธํฐํ์ด์ค์์ IP ์ถ์ถ
|
|
107
|
-
ip = `ifconfig en0 2>/dev/null | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}'`.strip
|
|
108
|
-
ip = `ifconfig en1 2>/dev/null | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}'`.strip if ip.empty?
|
|
109
|
-
|
|
110
|
-
# ๋ค๋ฅธ ์ธํฐํ์ด์ค ๊ฒ์
|
|
111
|
-
if ip.empty?
|
|
112
|
-
ip = `ifconfig | grep 'inet ' | grep -v 127.0.0.1 | grep -v '::1' | head -1 | awk '{print $2}'`.strip
|
|
113
|
-
end
|
|
114
|
-
else
|
|
115
|
-
# Linux
|
|
116
|
-
ip = `hostname -I 2>/dev/null | awk '{print $1}'`.strip
|
|
117
|
-
|
|
118
|
-
if ip.empty?
|
|
119
|
-
ip = `ip addr show | grep 'inet ' | grep -v 127.0.0.1 | grep -v '::1' | head -1 | awk '{print $2}' | cut -d/ -f1`.strip
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# ์ฌ์ ํ ๋น์ด์๋ค๋ฉด ์๋ ์
๋ ฅ
|
|
124
|
-
if ip.empty? || !valid_ip?(ip)
|
|
125
|
-
puts "\nโ ๏ธ ๋ด๋ถ IP๋ฅผ ์๋์ผ๋ก ๊ฐ์งํ ์ ์์ต๋๋ค.".colorize(:yellow)
|
|
126
|
-
ip = @prompt.ask("๋ด๋ถ IP๋ฅผ ์ง์ ์
๋ ฅํด์ฃผ์ธ์ (์: 192.168.1.100):") do |q|
|
|
127
|
-
q.validate(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/, "์ฌ๋ฐ๋ฅธ IP ํ์์ ์
๋ ฅํด์ฃผ์ธ์")
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
ip
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def valid_ip?(ip)
|
|
135
|
-
return false if ip.nil? || ip.empty?
|
|
136
|
-
|
|
137
|
-
parts = ip.split('.')
|
|
138
|
-
return false unless parts.length == 4
|
|
139
|
-
|
|
140
|
-
parts.all? do |part|
|
|
141
|
-
num = part.to_i
|
|
142
|
-
num >= 0 && num <= 255 && part == num.to_s
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|