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.
@@ -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