tayo 0.1.13 → 0.2.2
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 +67 -75
- data/README.md +77 -68
- data/lib/tayo/cli.rb +4 -4
- data/lib/tayo/commands/cf.rb +196 -221
- data/lib/tayo/commands/gh.rb +0 -9
- data/lib/tayo/commands/init.rb +35 -35
- data/lib/tayo/commands/proxy.rb +97 -0
- data/lib/tayo/proxy/cloudflare_client.rb +323 -0
- data/lib/tayo/proxy/docker_manager.rb +150 -0
- data/lib/tayo/proxy/network_config.rb +147 -0
- data/lib/tayo/proxy/traefik_config.rb +303 -0
- data/lib/tayo/proxy/welcome_service.rb +337 -0
- data/lib/tayo/version.rb +1 -1
- data/lib/templates/welcome/Dockerfile +14 -0
- data/lib/templates/welcome/index.html +173 -0
- metadata +24 -6
- data/CLAUDE.md +0 -58
- data/lib/tayo/commands/base.rb +0 -13
- data/lib/tayo/commands/sqlite.rb +0 -413
- data/scripts/setup_rubygems_key.sh +0 -60
data/lib/tayo/commands/cf.rb
CHANGED
|
@@ -12,129 +12,100 @@ module Tayo
|
|
|
12
12
|
def execute
|
|
13
13
|
puts "☁️ Cloudflare DNS 설정을 시작합니다...".colorize(:green)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return
|
|
18
|
-
end
|
|
15
|
+
# 1. Cloudflare 토큰 생성 페이지 열기 및 권한 안내
|
|
16
|
+
open_token_creation_page
|
|
19
17
|
|
|
20
|
-
# --- 로직 순서 변경 ---
|
|
21
|
-
|
|
22
18
|
# 2. 토큰 입력받기
|
|
23
19
|
token = get_cloudflare_token
|
|
24
|
-
|
|
25
|
-
# 3. Cloudflare 존 선택 및 도메인 구성 (새로운 방식)
|
|
26
|
-
domain_info = configure_domain_from_zones(token)
|
|
27
|
-
selected_zone = domain_info[:selected_zone_object]
|
|
28
20
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
|
|
21
|
+
# 3. Cloudflare API로 도메인 목록 조회 및 선택
|
|
22
|
+
selected_zone = select_cloudflare_zone(token)
|
|
23
|
+
|
|
24
|
+
# 4. 기존 레코드 목록 표시
|
|
25
|
+
show_existing_records(token, selected_zone)
|
|
26
|
+
|
|
27
|
+
# 5. 서비스 도메인 입력받기
|
|
28
|
+
domain_info = get_domain_input(selected_zone)
|
|
29
|
+
|
|
30
|
+
# 6. 홈서버 연결 정보 입력받기
|
|
31
|
+
server_info = get_server_info
|
|
32
|
+
|
|
33
|
+
# 7. DNS 레코드 추가/수정
|
|
34
|
+
setup_dns_record(token, selected_zone, domain_info, server_info)
|
|
35
|
+
|
|
36
|
+
# 8. config/deploy.yml 업데이트
|
|
37
|
+
update_deploy_config(domain_info, server_info)
|
|
37
38
|
|
|
38
39
|
puts "\n🎉 Cloudflare DNS 설정이 완료되었습니다!".colorize(:green)
|
|
39
|
-
|
|
40
|
-
#
|
|
40
|
+
|
|
41
|
+
# 변경사항 커밋
|
|
41
42
|
commit_cloudflare_changes(domain_info)
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
private
|
|
45
46
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
def get_domain_input(zone)
|
|
48
|
+
prompt = TTY::Prompt.new
|
|
49
|
+
zone_name = zone['name']
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
puts "📝 서비스 도메인을 설정합니다.".colorize(:yellow)
|
|
52
|
+
puts " 선택된 Zone: #{zone_name}".colorize(:gray)
|
|
53
|
+
|
|
54
|
+
use_subdomain = prompt.yes?("서브도메인을 사용하시겠습니까? (예: app.#{zone_name})")
|
|
55
|
+
|
|
56
|
+
if use_subdomain
|
|
57
|
+
subdomain = prompt.ask("서브도메인을 입력하세요 (예: app, api, www):") do |q|
|
|
58
|
+
q.validate(/\A[a-zA-Z0-9-]+\z/, "올바른 서브도메인을 입력해주세요 (영문, 숫자, 하이픈만 가능)")
|
|
59
|
+
end
|
|
60
|
+
full_domain = "#{subdomain}.#{zone_name}"
|
|
61
|
+
{ type: :subdomain, domain: full_domain, zone: zone_name, subdomain: subdomain }
|
|
62
|
+
else
|
|
63
|
+
{ type: :root, domain: zone_name, zone: zone_name }
|
|
60
64
|
end
|
|
61
|
-
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get_server_info
|
|
62
68
|
prompt = TTY::Prompt.new
|
|
63
|
-
# 사용자가 Zone을 이름으로 선택하고, 선택 시 전체 Zone 객체를 반환하도록 설정
|
|
64
|
-
zone_choices = zones.map { |zone| { name: "#{zone['name']} (#{zone['status']})", value: zone } }
|
|
65
|
-
|
|
66
|
-
selected_zone = prompt.select("설정할 도메인(Zone)을 선택하세요:", zone_choices, filter: true, per_page: 10)
|
|
67
|
-
zone_name = selected_zone['name']
|
|
68
|
-
puts "✅ 선택된 Zone: #{zone_name}".colorize(:green)
|
|
69
|
-
|
|
70
|
-
domain_type = prompt.select("\n어떤 종류의 도메인을 설정하시겠습니까?", [
|
|
71
|
-
{ name: "루트 도메인 (@) - 예: #{zone_name}", value: :root },
|
|
72
|
-
{ name: "서브도메인 - 예: www.#{zone_name}", value: :subdomain }
|
|
73
|
-
])
|
|
74
|
-
|
|
75
|
-
if domain_type == :root
|
|
76
|
-
return {
|
|
77
|
-
type: :root,
|
|
78
|
-
domain: zone_name,
|
|
79
|
-
zone: zone_name,
|
|
80
|
-
selected_zone_object: selected_zone
|
|
81
|
-
}
|
|
82
|
-
else # :subdomain
|
|
83
|
-
subdomain_part = prompt.ask("사용할 서브도메인을 입력하세요 (예: www, api):") do |q|
|
|
84
|
-
q.required true
|
|
85
|
-
q.validate(/^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/, "유효한 서브도메인을 입력해주세요 (특수문자, . 사용 불가)")
|
|
86
|
-
end
|
|
87
69
|
|
|
88
|
-
|
|
89
|
-
puts "✅ 설정할 전체 도메인: #{full_domain}".colorize(:green)
|
|
70
|
+
puts "\n🖥️ 홈서버 연결 정보를 입력합니다.".colorize(:yellow)
|
|
90
71
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
domain: full_domain,
|
|
94
|
-
zone: zone_name,
|
|
95
|
-
subdomain: subdomain_part.downcase,
|
|
96
|
-
selected_zone_object: selected_zone
|
|
97
|
-
}
|
|
72
|
+
server_address = prompt.ask("홈서버 IP 또는 도메인을 입력하세요:") do |q|
|
|
73
|
+
q.validate(/\A.+\z/, "서버 정보를 입력해주세요")
|
|
98
74
|
end
|
|
75
|
+
|
|
76
|
+
ssh_user = prompt.ask("SSH 사용자 계정을 입력하세요:", default: "root")
|
|
77
|
+
|
|
78
|
+
# IP인지 도메인인지 판단
|
|
79
|
+
is_ip = server_address.match?(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/)
|
|
80
|
+
record_type = is_ip ? 'A' : 'CNAME'
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
address: server_address,
|
|
84
|
+
ssh_user: ssh_user,
|
|
85
|
+
record_type: record_type
|
|
86
|
+
}
|
|
99
87
|
end
|
|
100
88
|
|
|
101
89
|
def open_token_creation_page
|
|
102
90
|
puts "\n🔑 Cloudflare API 토큰이 필요합니다.".colorize(:yellow)
|
|
91
|
+
puts "토큰 생성 페이지를 엽니다...".colorize(:cyan)
|
|
92
|
+
|
|
93
|
+
# Cloudflare API 토큰 생성 페이지 열기
|
|
94
|
+
system("open 'https://dash.cloudflare.com/profile/api-tokens'")
|
|
103
95
|
|
|
104
96
|
puts "\n다음 권한으로 토큰을 생성해주세요:".colorize(:yellow)
|
|
105
97
|
puts ""
|
|
106
98
|
puts "한국어 화면:".colorize(:gray)
|
|
107
|
-
puts "• 영역 → DNS → 읽기".colorize(:white)
|
|
108
99
|
puts "• 영역 → DNS → 편집".colorize(:white)
|
|
109
100
|
puts " (영역 리소스는 '모든 영역' 선택)".colorize(:gray)
|
|
110
101
|
puts ""
|
|
111
102
|
puts "English:".colorize(:gray)
|
|
112
|
-
puts "• Zone → DNS → Read".colorize(:white)
|
|
113
103
|
puts "• Zone → DNS → Edit".colorize(:white)
|
|
114
104
|
puts " (Zone Resources: Select 'All zones')".colorize(:gray)
|
|
115
105
|
puts ""
|
|
116
|
-
|
|
117
|
-
puts "토큰 생성 페이지를 엽니다...".colorize(:cyan)
|
|
118
|
-
|
|
119
|
-
system("open 'https://dash.cloudflare.com/profile/api-tokens'")
|
|
120
106
|
end
|
|
121
107
|
|
|
122
108
|
def get_cloudflare_token
|
|
123
|
-
existing_token = load_saved_token
|
|
124
|
-
|
|
125
|
-
if existing_token
|
|
126
|
-
puts "💾 저장된 토큰을 발견했습니다.".colorize(:cyan)
|
|
127
|
-
if test_cloudflare_token(existing_token)
|
|
128
|
-
puts "✅ 저장된 토큰이 유효합니다.".colorize(:green)
|
|
129
|
-
return existing_token
|
|
130
|
-
else
|
|
131
|
-
puts "❌ 저장된 토큰이 만료되거나 무효합니다. 새 토큰을 입력해주세요.".colorize(:yellow)
|
|
132
|
-
open_token_creation_page
|
|
133
|
-
end
|
|
134
|
-
else
|
|
135
|
-
open_token_creation_page
|
|
136
|
-
end
|
|
137
|
-
|
|
138
109
|
prompt = TTY::Prompt.new
|
|
139
110
|
|
|
140
111
|
token = prompt.mask("생성된 Cloudflare API 토큰을 붙여넣으세요:")
|
|
@@ -144,9 +115,9 @@ module Tayo
|
|
|
144
115
|
exit 1
|
|
145
116
|
end
|
|
146
117
|
|
|
118
|
+
# 토큰 유효성 간단 확인
|
|
147
119
|
if test_cloudflare_token(token.strip)
|
|
148
120
|
puts "✅ 토큰이 확인되었습니다.".colorize(:green)
|
|
149
|
-
save_token(token.strip)
|
|
150
121
|
return token.strip
|
|
151
122
|
else
|
|
152
123
|
puts "❌ 토큰이 올바르지 않거나 권한이 부족합니다.".colorize(:red)
|
|
@@ -169,42 +140,27 @@ module Tayo
|
|
|
169
140
|
return false
|
|
170
141
|
end
|
|
171
142
|
|
|
172
|
-
def
|
|
173
|
-
|
|
174
|
-
return nil unless File.exist?(token_file)
|
|
143
|
+
def select_cloudflare_zone(token)
|
|
144
|
+
puts "\n🌐 Cloudflare 도메인 목록을 조회합니다...".colorize(:yellow)
|
|
175
145
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return token unless token.nil? || token.empty?
|
|
183
|
-
|
|
184
|
-
nil
|
|
185
|
-
rescue => e
|
|
186
|
-
puts "⚠️ 토큰 파일 읽기 중 오류가 발생했습니다: #{e.message}".colorize(:yellow)
|
|
187
|
-
nil
|
|
146
|
+
zones = get_cloudflare_zones(token)
|
|
147
|
+
|
|
148
|
+
if zones.empty?
|
|
149
|
+
puts "❌ Cloudflare에 등록된 도메인이 없습니다.".colorize(:red)
|
|
150
|
+
puts "먼저 https://dash.cloudflare.com 에서 도메인을 추가해주세요.".colorize(:cyan)
|
|
151
|
+
exit 1
|
|
188
152
|
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def save_token(token)
|
|
192
|
-
token_file = File.expand_path("~/.tayo")
|
|
193
|
-
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
|
194
153
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# Created: #{timestamp}
|
|
198
|
-
CLOUDFLARE_TOKEN=#{token}
|
|
199
|
-
CONTENT
|
|
154
|
+
prompt = TTY::Prompt.new
|
|
155
|
+
zone_choices = zones.map { |zone| "#{zone['name']} (#{zone['status']})" }
|
|
200
156
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
157
|
+
selected = prompt.select("도메인을 선택하세요:", zone_choices)
|
|
158
|
+
zone_name = selected.split(' ').first
|
|
159
|
+
|
|
160
|
+
selected_zone = zones.find { |zone| zone['name'] == zone_name }
|
|
161
|
+
puts "✅ 선택된 도메인: #{zone_name}".colorize(:green)
|
|
162
|
+
|
|
163
|
+
return selected_zone
|
|
208
164
|
end
|
|
209
165
|
|
|
210
166
|
def get_cloudflare_zones(token)
|
|
@@ -230,15 +186,53 @@ module Tayo
|
|
|
230
186
|
exit 1
|
|
231
187
|
end
|
|
232
188
|
|
|
233
|
-
def
|
|
189
|
+
def show_existing_records(token, zone)
|
|
234
190
|
puts "\n🔍 기존 DNS 레코드를 확인합니다...".colorize(:yellow)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
191
|
+
|
|
192
|
+
zone_id = zone['id']
|
|
193
|
+
zone_name = zone['name']
|
|
194
|
+
|
|
195
|
+
# Zone의 모든 A/CNAME 레코드 조회
|
|
196
|
+
records = get_all_dns_records(token, zone_id, ['A', 'CNAME'])
|
|
197
|
+
|
|
198
|
+
if records.empty?
|
|
199
|
+
puts " 등록된 A/CNAME 레코드가 없습니다.".colorize(:gray)
|
|
200
|
+
else
|
|
201
|
+
puts " #{zone_name}의 기존 레코드:".colorize(:cyan)
|
|
202
|
+
records.each do |record|
|
|
203
|
+
puts " • #{record['name']} → #{record['content']} (#{record['type']})".colorize(:white)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
puts ""
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def get_all_dns_records(token, zone_id, types)
|
|
211
|
+
records = []
|
|
212
|
+
|
|
213
|
+
types.each do |type|
|
|
214
|
+
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
|
215
|
+
uri.query = URI.encode_www_form({ type: type })
|
|
216
|
+
|
|
217
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
218
|
+
http.use_ssl = true
|
|
219
|
+
|
|
220
|
+
request = Net::HTTP::Get.new(uri)
|
|
221
|
+
request['Authorization'] = "Bearer #{token}"
|
|
222
|
+
request['Content-Type'] = 'application/json'
|
|
223
|
+
|
|
224
|
+
response = http.request(request)
|
|
225
|
+
|
|
226
|
+
if response.code == '200'
|
|
227
|
+
data = JSON.parse(response.body)
|
|
228
|
+
records.concat(data['result'] || [])
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
records.sort_by { |r| r['name'] }
|
|
233
|
+
rescue => e
|
|
234
|
+
puts "⚠️ DNS 레코드 조회 중 오류: #{e.message}".colorize(:yellow)
|
|
235
|
+
[]
|
|
242
236
|
end
|
|
243
237
|
|
|
244
238
|
def get_dns_records(token, zone_id, name, types)
|
|
@@ -246,7 +240,10 @@ module Tayo
|
|
|
246
240
|
|
|
247
241
|
types.each do |type|
|
|
248
242
|
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
|
249
|
-
uri.query = URI.encode_www_form({
|
|
243
|
+
uri.query = URI.encode_www_form({
|
|
244
|
+
type: type,
|
|
245
|
+
name: name
|
|
246
|
+
})
|
|
250
247
|
|
|
251
248
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
252
249
|
http.use_ssl = true
|
|
@@ -268,77 +265,42 @@ module Tayo
|
|
|
268
265
|
puts "❌ DNS 레코드 조회 중 오류: #{e.message}".colorize(:red)
|
|
269
266
|
return []
|
|
270
267
|
end
|
|
271
|
-
|
|
272
|
-
def setup_dns_record(token, zone, domain_info,
|
|
268
|
+
|
|
269
|
+
def setup_dns_record(token, zone, domain_info, server_info)
|
|
273
270
|
puts "\n⚙️ DNS 레코드를 설정합니다...".colorize(:yellow)
|
|
274
|
-
|
|
275
|
-
prompt = TTY::Prompt.new
|
|
276
|
-
|
|
277
|
-
server_info = prompt.ask("연결할 서버 IP 또는 도메인을 입력하세요:") do |q|
|
|
278
|
-
q.validate(/\A.+\z/, "서버 정보를 입력해주세요")
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
ssh_user = prompt.ask("SSH 사용자 계정을 입력하세요:", default: "root")
|
|
282
|
-
|
|
283
|
-
is_ip = server_info.match?(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/)
|
|
284
|
-
record_type = is_ip ? 'A' : 'CNAME'
|
|
285
|
-
|
|
271
|
+
|
|
286
272
|
zone_id = zone['id']
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
all_records = get_dns_records(token, zone_id, final_domain[:name], ['A', 'CNAME'])
|
|
291
|
-
|
|
292
|
-
is_already_configured = all_records.length == 1 &&
|
|
293
|
-
all_records.first['type'] == record_type &&
|
|
294
|
-
all_records.first['content'] == server_info
|
|
273
|
+
target_domain = domain_info[:domain]
|
|
274
|
+
server_address = server_info[:address]
|
|
275
|
+
record_type = server_info[:record_type]
|
|
295
276
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
puts " #{final_domain[:full_domain]} → #{server_info} (#{record_type} 레코드)".colorize(:gray)
|
|
299
|
-
else
|
|
300
|
-
# [수정됨] 기존 레코드가 있으면 사용자에게 확인을 받습니다.
|
|
301
|
-
if all_records.any?
|
|
302
|
-
puts "\n⚠️ '#{final_domain[:full_domain]}'에 이미 설정된 DNS 레코드가 있습니다.".colorize(:yellow)
|
|
303
|
-
puts "--------------------------------------------------"
|
|
304
|
-
all_records.each do |record|
|
|
305
|
-
puts " - 타입: ".ljust(10) + "#{record['type']}".colorize(:cyan)
|
|
306
|
-
puts " 내용: ".ljust(10) + "#{record['content']}".colorize(:cyan)
|
|
307
|
-
puts " 프록시: ".ljust(10) + "#{record['proxied'] ? '활성' : '비활성'}".colorize(:cyan)
|
|
308
|
-
puts " "
|
|
309
|
-
end
|
|
310
|
-
puts "--------------------------------------------------"
|
|
277
|
+
# 대상 도메인의 기존 A/CNAME 레코드 확인
|
|
278
|
+
existing_records = get_dns_records(token, zone_id, target_domain, ['A', 'CNAME'])
|
|
311
279
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
puts "❌ DNS 설정이 사용자에 의해 취소되었습니다. 스크립트를 종료합니다.".colorize(:red)
|
|
315
|
-
exit 0
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
puts "\n✅ 사용자가 승인하여 기존 레코드를 삭제하고 새 레코드를 생성합니다.".colorize(:green)
|
|
319
|
-
all_records.each do |record|
|
|
320
|
-
delete_dns_record(token, zone_id, record['id'])
|
|
321
|
-
end
|
|
322
|
-
create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
|
|
280
|
+
if existing_records.any?
|
|
281
|
+
existing_record = existing_records.first
|
|
323
282
|
|
|
283
|
+
# 동일한 타입이고 같은 값이면 건너뛰기
|
|
284
|
+
if existing_record['type'] == record_type && existing_record['content'] == server_address
|
|
285
|
+
puts "✅ DNS 레코드가 이미 올바르게 설정되어 있습니다.".colorize(:green)
|
|
286
|
+
puts " #{target_domain} → #{server_address} (#{record_type} 레코드)".colorize(:gray)
|
|
324
287
|
else
|
|
325
|
-
#
|
|
326
|
-
|
|
288
|
+
# 타입이 다르거나 값이 다른 경우 삭제 후 재생성
|
|
289
|
+
puts "⚠️ 기존 레코드를 삭제하고 새로 생성합니다.".colorize(:yellow)
|
|
290
|
+
puts " 기존: #{existing_record['content']} (#{existing_record['type']}) → 새로운: #{server_address} (#{record_type})".colorize(:gray)
|
|
291
|
+
|
|
292
|
+
# 기존 레코드 삭제
|
|
293
|
+
delete_dns_record(token, zone_id, existing_record['id'])
|
|
294
|
+
|
|
295
|
+
# 새 레코드 생성
|
|
296
|
+
create_dns_record(token, zone_id, target_domain, record_type, server_address)
|
|
327
297
|
end
|
|
298
|
+
else
|
|
299
|
+
# DNS 레코드 생성
|
|
300
|
+
create_dns_record(token, zone_id, target_domain, record_type, server_address)
|
|
328
301
|
end
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
@server_info = server_info
|
|
332
|
-
@ssh_user = ssh_user
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def determine_final_domain(domain_info, zone_name, existing_records)
|
|
336
|
-
case domain_info[:type]
|
|
337
|
-
when :root
|
|
338
|
-
{ name: zone_name, full_domain: zone_name }
|
|
339
|
-
when :subdomain
|
|
340
|
-
{ name: domain_info[:domain], full_domain: domain_info[:domain] }
|
|
341
|
-
end
|
|
302
|
+
|
|
303
|
+
puts " #{target_domain} → #{server_address}".colorize(:cyan)
|
|
342
304
|
end
|
|
343
305
|
|
|
344
306
|
def create_dns_record(token, zone_id, name, type, content)
|
|
@@ -354,8 +316,7 @@ module Tayo
|
|
|
354
316
|
type: type,
|
|
355
317
|
name: name,
|
|
356
318
|
content: content,
|
|
357
|
-
ttl: 300
|
|
358
|
-
proxied: true
|
|
319
|
+
ttl: 300
|
|
359
320
|
}
|
|
360
321
|
|
|
361
322
|
request.body = data.to_json
|
|
@@ -363,7 +324,7 @@ module Tayo
|
|
|
363
324
|
|
|
364
325
|
if response.code == '200'
|
|
365
326
|
puts "✅ DNS 레코드가 생성되었습니다.".colorize(:green)
|
|
366
|
-
puts " #{name} → #{content} (#{type}
|
|
327
|
+
puts " #{name} → #{content} (#{type} 레코드)".colorize(:gray)
|
|
367
328
|
else
|
|
368
329
|
puts "❌ DNS 레코드 생성에 실패했습니다: #{response.code}".colorize(:red)
|
|
369
330
|
puts response.body
|
|
@@ -385,78 +346,92 @@ module Tayo
|
|
|
385
346
|
|
|
386
347
|
response = http.request(request)
|
|
387
348
|
|
|
388
|
-
|
|
349
|
+
if response.code == '200'
|
|
350
|
+
puts "✅ 기존 DNS 레코드가 삭제되었습니다.".colorize(:green)
|
|
351
|
+
else
|
|
389
352
|
puts "❌ DNS 레코드 삭제에 실패했습니다: #{response.code}".colorize(:red)
|
|
390
353
|
puts response.body
|
|
354
|
+
exit 1
|
|
391
355
|
end
|
|
392
356
|
rescue => e
|
|
393
357
|
puts "❌ DNS 레코드 삭제 중 오류: #{e.message}".colorize(:red)
|
|
358
|
+
exit 1
|
|
394
359
|
end
|
|
395
360
|
|
|
396
|
-
def update_deploy_config(domain_info)
|
|
361
|
+
def update_deploy_config(domain_info, server_info)
|
|
397
362
|
puts "\n📝 배포 설정을 업데이트합니다...".colorize(:yellow)
|
|
398
|
-
|
|
363
|
+
|
|
399
364
|
config_file = "config/deploy.yml"
|
|
400
|
-
|
|
365
|
+
final_domain = domain_info[:domain]
|
|
366
|
+
server_address = server_info[:address]
|
|
367
|
+
ssh_user = server_info[:ssh_user]
|
|
368
|
+
|
|
401
369
|
unless File.exist?(config_file)
|
|
402
370
|
puts "⚠️ config/deploy.yml 파일이 없습니다.".colorize(:yellow)
|
|
403
371
|
return
|
|
404
372
|
end
|
|
405
|
-
|
|
373
|
+
|
|
406
374
|
content = File.read(config_file)
|
|
407
|
-
|
|
375
|
+
|
|
408
376
|
# proxy.host 설정 업데이트
|
|
409
377
|
if content.include?("proxy:")
|
|
410
|
-
content.gsub!(/(\s+host:\s+).*$/, "\\1#{
|
|
378
|
+
content.gsub!(/(\s+host:\s+).*$/, "\\1#{final_domain}")
|
|
411
379
|
else
|
|
412
|
-
|
|
380
|
+
# proxy 섹션이 없으면 추가
|
|
381
|
+
proxy_config = "\n# Proxy configuration\nproxy:\n ssl: true\n host: #{final_domain}\n"
|
|
413
382
|
content += proxy_config
|
|
414
383
|
end
|
|
415
|
-
|
|
384
|
+
|
|
416
385
|
# servers 설정 업데이트
|
|
417
386
|
if content.match?(/servers:\s*\n\s*web:\s*\n\s*-\s*/)
|
|
418
|
-
content.gsub!(/(\s*servers:\s*\n\s*web:\s*\n\s*-\s*)[\
|
|
387
|
+
content.gsub!(/(\s*servers:\s*\n\s*web:\s*\n\s*-\s*)[\d.]+/, "\\1#{server_address}")
|
|
419
388
|
end
|
|
420
|
-
|
|
389
|
+
|
|
421
390
|
# ssh user 설정 업데이트
|
|
422
|
-
if
|
|
391
|
+
if ssh_user && ssh_user != "root"
|
|
423
392
|
if content.match?(/^ssh:/)
|
|
424
|
-
|
|
393
|
+
# 기존 ssh 섹션 업데이트
|
|
394
|
+
content.gsub!(/^ssh:\s*\n\s*user:\s*\w+/, "ssh:\n user: #{ssh_user}")
|
|
425
395
|
else
|
|
396
|
+
# ssh 섹션 추가 (accessories 섹션 앞에 추가)
|
|
426
397
|
if content.match?(/^# Use accessory services/)
|
|
427
|
-
content.gsub!(/^# Use accessory services/, "# Use a different ssh user than root\nssh:\n user: #{
|
|
398
|
+
content.gsub!(/^# Use accessory services/, "# Use a different ssh user than root\nssh:\n user: #{ssh_user}\n\n# Use accessory services")
|
|
428
399
|
else
|
|
429
|
-
|
|
400
|
+
# 파일 끝에 추가
|
|
401
|
+
content += "\n# Use a different ssh user than root\nssh:\n user: #{ssh_user}\n"
|
|
430
402
|
end
|
|
431
403
|
end
|
|
432
404
|
end
|
|
433
|
-
|
|
405
|
+
|
|
434
406
|
File.write(config_file, content)
|
|
435
407
|
puts "✅ config/deploy.yml이 업데이트되었습니다.".colorize(:green)
|
|
436
|
-
puts " proxy.host: #{
|
|
437
|
-
puts " servers.web: #{
|
|
438
|
-
if
|
|
439
|
-
puts " ssh.user: #{@ssh_user}".colorize(:gray)
|
|
440
|
-
end
|
|
408
|
+
puts " proxy.host: #{final_domain}".colorize(:gray)
|
|
409
|
+
puts " servers.web: #{server_address}".colorize(:gray)
|
|
410
|
+
puts " ssh.user: #{ssh_user}".colorize(:gray) if ssh_user && ssh_user != "root"
|
|
441
411
|
end
|
|
442
412
|
|
|
443
413
|
def commit_cloudflare_changes(domain_info)
|
|
444
414
|
puts "\n📝 변경사항을 Git에 커밋합니다...".colorize(:yellow)
|
|
445
415
|
|
|
446
|
-
|
|
416
|
+
# 변경된 파일이 있는지 확인
|
|
417
|
+
status_output = `git status --porcelain`.strip
|
|
447
418
|
|
|
448
419
|
if status_output.empty?
|
|
449
|
-
puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:
|
|
420
|
+
puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:yellow)
|
|
450
421
|
return
|
|
451
422
|
end
|
|
452
423
|
|
|
453
|
-
|
|
424
|
+
# Git add
|
|
425
|
+
system("git add -A")
|
|
454
426
|
|
|
455
|
-
|
|
427
|
+
# Commit 메시지 생성
|
|
428
|
+
commit_message = "Configure Cloudflare DNS settings\n\n- Setup DNS for domain: #{domain_info[:domain]}\n- Configure server IP: #{domain_info[:server_ip]}\n- Update deployment configuration\n- Add proxy host settings\n\n🤖 Generated with Tayo"
|
|
456
429
|
|
|
430
|
+
# Commit 실행
|
|
457
431
|
if system("git commit -m \"#{commit_message}\"")
|
|
458
432
|
puts "✅ 변경사항이 성공적으로 커밋되었습니다.".colorize(:green)
|
|
459
433
|
|
|
434
|
+
# GitHub에 푸시
|
|
460
435
|
if system("git push", out: File::NULL, err: File::NULL)
|
|
461
436
|
puts "✅ 변경사항이 GitHub에 푸시되었습니다.".colorize(:green)
|
|
462
437
|
else
|
data/lib/tayo/commands/gh.rb
CHANGED
|
@@ -12,11 +12,6 @@ module Tayo
|
|
|
12
12
|
def execute
|
|
13
13
|
puts "🚀 GitHub 저장소 및 컨테이너 레지스트리 설정을 시작합니다...".colorize(:green)
|
|
14
14
|
|
|
15
|
-
unless rails_project?
|
|
16
|
-
puts "❌ Rails 프로젝트가 아닙니다. Rails 프로젝트 루트에서 실행해주세요.".colorize(:red)
|
|
17
|
-
return
|
|
18
|
-
end
|
|
19
|
-
|
|
20
15
|
puts "\n[1/7] GitHub CLI 설치 확인".colorize(:blue)
|
|
21
16
|
check_github_cli
|
|
22
17
|
|
|
@@ -50,10 +45,6 @@ module Tayo
|
|
|
50
45
|
|
|
51
46
|
private
|
|
52
47
|
|
|
53
|
-
def rails_project?
|
|
54
|
-
File.exist?("Gemfile") && File.exist?("config/application.rb")
|
|
55
|
-
end
|
|
56
|
-
|
|
57
48
|
def check_github_cli
|
|
58
49
|
if system("gh --version", out: File::NULL, err: File::NULL)
|
|
59
50
|
puts "✅ GitHub CLI가 이미 설치되어 있습니다.".colorize(:green)
|