tayo 0.1.12 → 0.2.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 +67 -57
- data/README.md +83 -45
- data/lib/tayo/cli.rb +6 -0
- data/lib/tayo/commands/cf.rb +116 -162
- 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 -3
- data/CLAUDE.md +0 -58
data/lib/tayo/commands/cf.rb
CHANGED
|
@@ -17,27 +17,30 @@ module Tayo
|
|
|
17
17
|
return
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
#
|
|
20
|
+
# 1. 도메인 입력받기
|
|
21
|
+
domain_info = get_domain_input
|
|
21
22
|
|
|
22
|
-
# 2. 토큰
|
|
23
|
+
# 2. Cloudflare 토큰 생성 페이지 열기 및 권한 안내
|
|
24
|
+
open_token_creation_page
|
|
25
|
+
|
|
26
|
+
# 3. 토큰 입력받기
|
|
23
27
|
token = get_cloudflare_token
|
|
24
28
|
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# 4. 기존 DNS 레코드 확인 (참고용)
|
|
29
|
+
# 4. Cloudflare API로 도메인 목록 조회 및 선택
|
|
30
|
+
selected_zone = select_cloudflare_zone(token)
|
|
31
|
+
|
|
32
|
+
# 5. 루트 도메인 레코드 확인
|
|
30
33
|
existing_records = check_existing_records(token, selected_zone, domain_info)
|
|
31
34
|
|
|
32
|
-
#
|
|
35
|
+
# 6. DNS 레코드 추가/수정
|
|
33
36
|
setup_dns_record(token, selected_zone, domain_info, existing_records)
|
|
34
37
|
|
|
35
|
-
#
|
|
38
|
+
# 7. config/deploy.yml 업데이트
|
|
36
39
|
update_deploy_config(domain_info)
|
|
37
40
|
|
|
38
41
|
puts "\n🎉 Cloudflare DNS 설정이 완료되었습니다!".colorize(:green)
|
|
39
42
|
|
|
40
|
-
#
|
|
43
|
+
# 변경사항 커밋
|
|
41
44
|
commit_cloudflare_changes(domain_info)
|
|
42
45
|
end
|
|
43
46
|
|
|
@@ -47,59 +50,31 @@ module Tayo
|
|
|
47
50
|
File.exist?("Gemfile") && File.exist?("config/application.rb")
|
|
48
51
|
end
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
puts "\n🌐 Cloudflare 계정의 도메인 목록을 조회합니다...".colorize(:yellow)
|
|
53
|
+
def get_domain_input
|
|
54
|
+
prompt = TTY::Prompt.new
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
puts "\n📝 배포할 도메인을 설정합니다.".colorize(:yellow)
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
puts "먼저 https://dash.cloudflare.com 에서 도메인을 추가해주세요.".colorize(:cyan)
|
|
59
|
-
exit 1
|
|
58
|
+
domain = prompt.ask("배포할 도메인을 입력하세요 (예: myapp.com, api.example.com):") do |q|
|
|
59
|
+
q.validate(/\A[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/, "올바른 도메인 형식을 입력해주세요 (예: myapp.com)")
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
88
|
-
full_domain = "#{subdomain_part.downcase}.#{zone_name}"
|
|
89
|
-
puts "✅ 설정할 전체 도메인: #{full_domain}".colorize(:green)
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
type: :subdomain,
|
|
93
|
-
domain: full_domain,
|
|
94
|
-
zone: zone_name,
|
|
95
|
-
subdomain: subdomain_part.downcase,
|
|
96
|
-
selected_zone_object: selected_zone
|
|
97
|
-
}
|
|
62
|
+
# 도메인이 루트인지 서브도메인인지 판단
|
|
63
|
+
parts = domain.split('.')
|
|
64
|
+
if parts.length == 2
|
|
65
|
+
{ type: :root, domain: domain, zone: domain }
|
|
66
|
+
else
|
|
67
|
+
zone = parts[-2..-1].join('.')
|
|
68
|
+
{ type: :subdomain, domain: domain, zone: zone, subdomain: parts[0..-3].join('.') }
|
|
98
69
|
end
|
|
99
70
|
end
|
|
100
71
|
|
|
101
72
|
def open_token_creation_page
|
|
102
73
|
puts "\n🔑 Cloudflare API 토큰이 필요합니다.".colorize(:yellow)
|
|
74
|
+
puts "토큰 생성 페이지를 엽니다...".colorize(:cyan)
|
|
75
|
+
|
|
76
|
+
# Cloudflare API 토큰 생성 페이지 열기
|
|
77
|
+
system("open 'https://dash.cloudflare.com/profile/api-tokens'")
|
|
103
78
|
|
|
104
79
|
puts "\n다음 권한으로 토큰을 생성해주세요:".colorize(:yellow)
|
|
105
80
|
puts ""
|
|
@@ -113,28 +88,9 @@ module Tayo
|
|
|
113
88
|
puts "• Zone → DNS → Edit".colorize(:white)
|
|
114
89
|
puts " (Zone Resources: Select 'All zones')".colorize(:gray)
|
|
115
90
|
puts ""
|
|
116
|
-
|
|
117
|
-
puts "토큰 생성 페이지를 엽니다...".colorize(:cyan)
|
|
118
|
-
|
|
119
|
-
system("open 'https://dash.cloudflare.com/profile/api-tokens'")
|
|
120
91
|
end
|
|
121
92
|
|
|
122
93
|
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
94
|
prompt = TTY::Prompt.new
|
|
139
95
|
|
|
140
96
|
token = prompt.mask("생성된 Cloudflare API 토큰을 붙여넣으세요:")
|
|
@@ -144,9 +100,9 @@ module Tayo
|
|
|
144
100
|
exit 1
|
|
145
101
|
end
|
|
146
102
|
|
|
103
|
+
# 토큰 유효성 간단 확인
|
|
147
104
|
if test_cloudflare_token(token.strip)
|
|
148
105
|
puts "✅ 토큰이 확인되었습니다.".colorize(:green)
|
|
149
|
-
save_token(token.strip)
|
|
150
106
|
return token.strip
|
|
151
107
|
else
|
|
152
108
|
puts "❌ 토큰이 올바르지 않거나 권한이 부족합니다.".colorize(:red)
|
|
@@ -169,42 +125,27 @@ module Tayo
|
|
|
169
125
|
return false
|
|
170
126
|
end
|
|
171
127
|
|
|
172
|
-
def
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
token = token_line.split("=", 2)[1]&.strip
|
|
182
|
-
return token unless token.nil? || token.empty?
|
|
183
|
-
|
|
184
|
-
nil
|
|
185
|
-
rescue => e
|
|
186
|
-
puts "⚠️ 토큰 파일 읽기 중 오류가 발생했습니다: #{e.message}".colorize(:yellow)
|
|
187
|
-
nil
|
|
188
|
-
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
|
-
|
|
195
|
-
content = <<~CONTENT
|
|
196
|
-
# Tayo Configuration File
|
|
197
|
-
# Created: #{timestamp}
|
|
198
|
-
CLOUDFLARE_TOKEN=#{token}
|
|
199
|
-
CONTENT
|
|
200
|
-
|
|
201
|
-
begin
|
|
202
|
-
File.write(token_file, content)
|
|
203
|
-
File.chmod(0600, token_file)
|
|
204
|
-
puts "💾 토큰이 ~/.tayo 파일에 저장되었습니다.".colorize(:green)
|
|
205
|
-
rescue => e
|
|
206
|
-
puts "⚠️ 토큰 저장 중 오류가 발생했습니다: #{e.message}".colorize(:yellow)
|
|
128
|
+
def select_cloudflare_zone(token)
|
|
129
|
+
puts "\n🌐 Cloudflare 도메인 목록을 조회합니다...".colorize(:yellow)
|
|
130
|
+
|
|
131
|
+
zones = get_cloudflare_zones(token)
|
|
132
|
+
|
|
133
|
+
if zones.empty?
|
|
134
|
+
puts "❌ Cloudflare에 등록된 도메인이 없습니다.".colorize(:red)
|
|
135
|
+
puts "먼저 https://dash.cloudflare.com 에서 도메인을 추가해주세요.".colorize(:cyan)
|
|
136
|
+
exit 1
|
|
207
137
|
end
|
|
138
|
+
|
|
139
|
+
prompt = TTY::Prompt.new
|
|
140
|
+
zone_choices = zones.map { |zone| "#{zone['name']} (#{zone['status']})" }
|
|
141
|
+
|
|
142
|
+
selected = prompt.select("도메인을 선택하세요:", zone_choices)
|
|
143
|
+
zone_name = selected.split(' ').first
|
|
144
|
+
|
|
145
|
+
selected_zone = zones.find { |zone| zone['name'] == zone_name }
|
|
146
|
+
puts "✅ 선택된 도메인: #{zone_name}".colorize(:green)
|
|
147
|
+
|
|
148
|
+
return selected_zone
|
|
208
149
|
end
|
|
209
150
|
|
|
210
151
|
def get_cloudflare_zones(token)
|
|
@@ -233,10 +174,13 @@ module Tayo
|
|
|
233
174
|
def check_existing_records(token, zone, domain_info)
|
|
234
175
|
puts "\n🔍 기존 DNS 레코드를 확인합니다...".colorize(:yellow)
|
|
235
176
|
|
|
236
|
-
|
|
237
|
-
|
|
177
|
+
zone_id = zone['id']
|
|
178
|
+
zone_name = zone['name']
|
|
179
|
+
|
|
180
|
+
# 루트 도메인의 A/CNAME 레코드 확인
|
|
181
|
+
records = get_dns_records(token, zone_id, zone_name, ['A', 'CNAME'])
|
|
238
182
|
|
|
239
|
-
puts "
|
|
183
|
+
puts "기존 레코드: #{records.length}개 발견".colorize(:gray)
|
|
240
184
|
|
|
241
185
|
return records
|
|
242
186
|
end
|
|
@@ -246,7 +190,10 @@ module Tayo
|
|
|
246
190
|
|
|
247
191
|
types.each do |type|
|
|
248
192
|
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
|
249
|
-
uri.query = URI.encode_www_form({
|
|
193
|
+
uri.query = URI.encode_www_form({
|
|
194
|
+
type: type,
|
|
195
|
+
name: name
|
|
196
|
+
})
|
|
250
197
|
|
|
251
198
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
252
199
|
http.use_ssl = true
|
|
@@ -268,74 +215,71 @@ module Tayo
|
|
|
268
215
|
puts "❌ DNS 레코드 조회 중 오류: #{e.message}".colorize(:red)
|
|
269
216
|
return []
|
|
270
217
|
end
|
|
271
|
-
|
|
218
|
+
|
|
272
219
|
def setup_dns_record(token, zone, domain_info, existing_records)
|
|
273
220
|
puts "\n⚙️ DNS 레코드를 설정합니다...".colorize(:yellow)
|
|
274
221
|
|
|
222
|
+
# 홈서버 IP/URL 입력받기
|
|
275
223
|
prompt = TTY::Prompt.new
|
|
276
224
|
|
|
277
|
-
server_info = prompt.ask("
|
|
225
|
+
server_info = prompt.ask("홈서버 IP 또는 도메인을 입력하세요:") do |q|
|
|
278
226
|
q.validate(/\A.+\z/, "서버 정보를 입력해주세요")
|
|
279
227
|
end
|
|
280
228
|
|
|
229
|
+
# SSH 사용자 계정 입력받기
|
|
281
230
|
ssh_user = prompt.ask("SSH 사용자 계정을 입력하세요:", default: "root")
|
|
282
231
|
|
|
232
|
+
# IP인지 도메인인지 판단
|
|
283
233
|
is_ip = server_info.match?(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/)
|
|
284
234
|
record_type = is_ip ? 'A' : 'CNAME'
|
|
285
235
|
|
|
286
236
|
zone_id = zone['id']
|
|
287
237
|
zone_name = zone['name']
|
|
288
238
|
|
|
239
|
+
# 도메인 정보에 따라 레코드 설정
|
|
289
240
|
final_domain = determine_final_domain(domain_info, zone_name, existing_records)
|
|
241
|
+
|
|
242
|
+
# 대상 도메인의 모든 A/CNAME 레코드 확인
|
|
290
243
|
all_records = get_dns_records(token, zone_id, final_domain[:name], ['A', 'CNAME'])
|
|
291
244
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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 "--------------------------------------------------"
|
|
311
|
-
|
|
312
|
-
message = "이 레코드를 삭제하고 새로 설정하시겠습니까? (이 작업은 되돌릴 수 없습니다)"
|
|
313
|
-
unless prompt.yes?(message)
|
|
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)
|
|
323
|
-
|
|
245
|
+
if all_records.any?
|
|
246
|
+
existing_record = all_records.first
|
|
247
|
+
|
|
248
|
+
# 동일한 타입이고 같은 값이면 건너뛰기
|
|
249
|
+
if existing_record['type'] == record_type && existing_record['content'] == server_info
|
|
250
|
+
puts "✅ DNS 레코드가 이미 올바르게 설정되어 있습니다.".colorize(:green)
|
|
251
|
+
puts " #{final_domain[:full_domain]} → #{server_info} (#{record_type} 레코드)".colorize(:gray)
|
|
324
252
|
else
|
|
325
|
-
#
|
|
253
|
+
# 타입이 다르거나 값이 다른 경우 삭제 후 재생성
|
|
254
|
+
puts "⚠️ 기존 레코드를 삭제하고 새로 생성합니다.".colorize(:yellow)
|
|
255
|
+
puts " 기존: #{existing_record['content']} (#{existing_record['type']}) → 새로운: #{server_info} (#{record_type})".colorize(:gray)
|
|
256
|
+
|
|
257
|
+
# 기존 레코드 삭제
|
|
258
|
+
delete_dns_record(token, zone_id, existing_record['id'])
|
|
259
|
+
|
|
260
|
+
# 새 레코드 생성
|
|
326
261
|
create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
|
|
327
262
|
end
|
|
263
|
+
else
|
|
264
|
+
# DNS 레코드 생성
|
|
265
|
+
create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
|
|
328
266
|
end
|
|
329
267
|
|
|
268
|
+
# 최종 도메인 정보 저장
|
|
330
269
|
@final_domain = final_domain[:full_domain]
|
|
331
270
|
@server_info = server_info
|
|
332
271
|
@ssh_user = ssh_user
|
|
333
272
|
end
|
|
334
|
-
|
|
273
|
+
|
|
335
274
|
def determine_final_domain(domain_info, zone_name, existing_records)
|
|
336
275
|
case domain_info[:type]
|
|
337
276
|
when :root
|
|
338
|
-
|
|
277
|
+
if existing_records.any?
|
|
278
|
+
puts "⚠️ 루트 도메인에 이미 레코드가 있습니다. app.#{zone_name}을 사용합니다.".colorize(:yellow)
|
|
279
|
+
{ name: "app.#{zone_name}", full_domain: "app.#{zone_name}" }
|
|
280
|
+
else
|
|
281
|
+
{ name: zone_name, full_domain: zone_name }
|
|
282
|
+
end
|
|
339
283
|
when :subdomain
|
|
340
284
|
{ name: domain_info[:domain], full_domain: domain_info[:domain] }
|
|
341
285
|
end
|
|
@@ -354,8 +298,7 @@ module Tayo
|
|
|
354
298
|
type: type,
|
|
355
299
|
name: name,
|
|
356
300
|
content: content,
|
|
357
|
-
ttl: 300
|
|
358
|
-
proxied: true
|
|
301
|
+
ttl: 300
|
|
359
302
|
}
|
|
360
303
|
|
|
361
304
|
request.body = data.to_json
|
|
@@ -363,7 +306,7 @@ module Tayo
|
|
|
363
306
|
|
|
364
307
|
if response.code == '200'
|
|
365
308
|
puts "✅ DNS 레코드가 생성되었습니다.".colorize(:green)
|
|
366
|
-
puts " #{name} → #{content} (#{type}
|
|
309
|
+
puts " #{name} → #{content} (#{type} 레코드)".colorize(:gray)
|
|
367
310
|
else
|
|
368
311
|
puts "❌ DNS 레코드 생성에 실패했습니다: #{response.code}".colorize(:red)
|
|
369
312
|
puts response.body
|
|
@@ -385,12 +328,16 @@ module Tayo
|
|
|
385
328
|
|
|
386
329
|
response = http.request(request)
|
|
387
330
|
|
|
388
|
-
|
|
331
|
+
if response.code == '200'
|
|
332
|
+
puts "✅ 기존 DNS 레코드가 삭제되었습니다.".colorize(:green)
|
|
333
|
+
else
|
|
389
334
|
puts "❌ DNS 레코드 삭제에 실패했습니다: #{response.code}".colorize(:red)
|
|
390
335
|
puts response.body
|
|
336
|
+
exit 1
|
|
391
337
|
end
|
|
392
338
|
rescue => e
|
|
393
339
|
puts "❌ DNS 레코드 삭제 중 오류: #{e.message}".colorize(:red)
|
|
340
|
+
exit 1
|
|
394
341
|
end
|
|
395
342
|
|
|
396
343
|
def update_deploy_config(domain_info)
|
|
@@ -409,23 +356,27 @@ module Tayo
|
|
|
409
356
|
if content.include?("proxy:")
|
|
410
357
|
content.gsub!(/(\s+host:\s+).*$/, "\\1#{@final_domain}")
|
|
411
358
|
else
|
|
359
|
+
# proxy 섹션이 없으면 추가
|
|
412
360
|
proxy_config = "\n# Proxy configuration\nproxy:\n ssl: true\n host: #{@final_domain}\n"
|
|
413
361
|
content += proxy_config
|
|
414
362
|
end
|
|
415
363
|
|
|
416
364
|
# servers 설정 업데이트
|
|
417
365
|
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*)[\
|
|
366
|
+
content.gsub!(/(\s*servers:\s*\n\s*web:\s*\n\s*-\s*)[\d.]+/, "\\1#{@server_info}")
|
|
419
367
|
end
|
|
420
368
|
|
|
421
369
|
# ssh user 설정 업데이트
|
|
422
370
|
if @ssh_user && @ssh_user != "root"
|
|
423
371
|
if content.match?(/^ssh:/)
|
|
372
|
+
# 기존 ssh 섹션 업데이트
|
|
424
373
|
content.gsub!(/^ssh:\s*\n\s*user:\s*\w+/, "ssh:\n user: #{@ssh_user}")
|
|
425
374
|
else
|
|
375
|
+
# ssh 섹션 추가 (accessories 섹션 앞에 추가)
|
|
426
376
|
if content.match?(/^# Use accessory services/)
|
|
427
377
|
content.gsub!(/^# Use accessory services/, "# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n\n# Use accessory services")
|
|
428
378
|
else
|
|
379
|
+
# 파일 끝에 추가
|
|
429
380
|
content += "\n# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n"
|
|
430
381
|
end
|
|
431
382
|
end
|
|
@@ -435,28 +386,31 @@ module Tayo
|
|
|
435
386
|
puts "✅ config/deploy.yml이 업데이트되었습니다.".colorize(:green)
|
|
436
387
|
puts " proxy.host: #{@final_domain}".colorize(:gray)
|
|
437
388
|
puts " servers.web: #{@server_info}".colorize(:gray)
|
|
438
|
-
if @ssh_user && @ssh_user != "root"
|
|
439
|
-
puts " ssh.user: #{@ssh_user}".colorize(:gray)
|
|
440
|
-
end
|
|
389
|
+
puts " ssh.user: #{@ssh_user}".colorize(:gray) if @ssh_user && @ssh_user != "root"
|
|
441
390
|
end
|
|
442
391
|
|
|
443
392
|
def commit_cloudflare_changes(domain_info)
|
|
444
393
|
puts "\n📝 변경사항을 Git에 커밋합니다...".colorize(:yellow)
|
|
445
394
|
|
|
446
|
-
|
|
395
|
+
# 변경된 파일이 있는지 확인
|
|
396
|
+
status_output = `git status --porcelain`.strip
|
|
447
397
|
|
|
448
398
|
if status_output.empty?
|
|
449
|
-
puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:
|
|
399
|
+
puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:yellow)
|
|
450
400
|
return
|
|
451
401
|
end
|
|
452
402
|
|
|
453
|
-
|
|
403
|
+
# Git add
|
|
404
|
+
system("git add -A")
|
|
454
405
|
|
|
455
|
-
|
|
406
|
+
# Commit 메시지 생성
|
|
407
|
+
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
408
|
|
|
409
|
+
# Commit 실행
|
|
457
410
|
if system("git commit -m \"#{commit_message}\"")
|
|
458
411
|
puts "✅ 변경사항이 성공적으로 커밋되었습니다.".colorize(:green)
|
|
459
412
|
|
|
413
|
+
# GitHub에 푸시
|
|
460
414
|
if system("git push", out: File::NULL, err: File::NULL)
|
|
461
415
|
puts "✅ 변경사항이 GitHub에 푸시되었습니다.".colorize(:green)
|
|
462
416
|
else
|
data/lib/tayo/commands/init.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "colorize"
|
|
4
|
-
|
|
4
|
+
require_relative "../dockerfile_modifier"
|
|
5
5
|
|
|
6
6
|
module Tayo
|
|
7
7
|
module Commands
|
|
@@ -15,9 +15,9 @@ module Tayo
|
|
|
15
15
|
end
|
|
16
16
|
commit_initial_state
|
|
17
17
|
check_orbstack
|
|
18
|
-
create_welcome_page
|
|
18
|
+
create_welcome_page
|
|
19
19
|
clear_docker_cache
|
|
20
|
-
ensure_dockerfile_exists
|
|
20
|
+
ensure_dockerfile_exists
|
|
21
21
|
commit_changes
|
|
22
22
|
puts "✅ Tayo가 성공적으로 설정되었습니다!".colorize(:green)
|
|
23
23
|
end
|
|
@@ -30,19 +30,19 @@ module Tayo
|
|
|
30
30
|
|
|
31
31
|
def check_orbstack
|
|
32
32
|
puts "🐳 OrbStack 상태를 확인합니다...".colorize(:yellow)
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
# OrbStack 실행 상태 확인
|
|
35
35
|
orbstack_running = system("pgrep -x OrbStack > /dev/null 2>&1")
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
if orbstack_running
|
|
38
38
|
puts "✅ OrbStack이 실행 중입니다.".colorize(:green)
|
|
39
39
|
else
|
|
40
40
|
puts "🚀 OrbStack을 시작합니다...".colorize(:yellow)
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
# OrbStack 실행
|
|
43
43
|
if system("open -a OrbStack")
|
|
44
44
|
puts "✅ OrbStack이 시작되었습니다.".colorize(:green)
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
# OrbStack이 완전히 시작될 때까지 잠시 대기
|
|
47
47
|
print "Docker 서비스가 준비될 때까지 대기 중".colorize(:yellow)
|
|
48
48
|
5.times do
|
|
@@ -50,7 +50,7 @@ module Tayo
|
|
|
50
50
|
print ".".colorize(:yellow)
|
|
51
51
|
end
|
|
52
52
|
puts ""
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
# Docker가 준비되었는지 확인
|
|
55
55
|
if system("docker ps > /dev/null 2>&1")
|
|
56
56
|
puts "✅ Docker가 준비되었습니다.".colorize(:green)
|
|
@@ -64,11 +64,11 @@ module Tayo
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
def ensure_dockerfile_exists
|
|
69
69
|
unless File.exist?("Dockerfile")
|
|
70
70
|
puts "🐳 Dockerfile이 없습니다. 기본 Dockerfile을 생성합니다...".colorize(:yellow)
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
# Rails 7의 기본 Dockerfile 생성
|
|
73
73
|
if system("rails app:update:bin")
|
|
74
74
|
system("./bin/rails generate dockerfile")
|
|
@@ -80,7 +80,7 @@ module Tayo
|
|
|
80
80
|
end
|
|
81
81
|
else
|
|
82
82
|
puts "✅ Dockerfile이 이미 존재합니다.".colorize(:green)
|
|
83
|
-
end
|
|
83
|
+
end
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def create_welcome_page
|
|
@@ -90,15 +90,15 @@ module Tayo
|
|
|
90
90
|
@welcome_page_created = false
|
|
91
91
|
return
|
|
92
92
|
end
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
puts "🎨 Welcome 페이지를 생성합니다...".colorize(:yellow)
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
# Welcome 컨트롤러 생성
|
|
97
97
|
system("rails generate controller Welcome index --skip-routes --no-helper --no-assets")
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
# 프로젝트 이름 가져오기
|
|
100
100
|
project_name = File.basename(Dir.pwd).gsub(/[-_]/, ' ').split.map(&:capitalize).join(' ')
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
# Welcome 페이지 HTML 생성
|
|
103
103
|
welcome_html = <<~HTML
|
|
104
104
|
<!DOCTYPE html>
|
|
@@ -188,7 +188,7 @@ module Tayo
|
|
|
188
188
|
<div class="container">
|
|
189
189
|
<h1>🏠 #{project_name}</h1>
|
|
190
190
|
<p class="subtitle">Welcome to your Tayo-powered Rails application!</p>
|
|
191
|
-
|
|
191
|
+
|
|
192
192
|
<div class="info-grid">
|
|
193
193
|
<div class="info-card">
|
|
194
194
|
<h3>📦 Container Ready</h3>
|
|
@@ -203,7 +203,7 @@ module Tayo
|
|
|
203
203
|
<p>Domain management simplified</p>
|
|
204
204
|
</div>
|
|
205
205
|
</div>
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
<div class="deploy-badge">
|
|
208
208
|
Deployed with Tayo 🎉
|
|
209
209
|
</div>
|
|
@@ -211,15 +211,15 @@ module Tayo
|
|
|
211
211
|
</body>
|
|
212
212
|
</html>
|
|
213
213
|
HTML
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
# Welcome 뷰 파일에 저장
|
|
216
216
|
welcome_view_path = "app/views/welcome/index.html.erb"
|
|
217
217
|
File.write(welcome_view_path, welcome_html)
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
# routes.rb 업데이트
|
|
220
220
|
routes_file = "config/routes.rb"
|
|
221
221
|
routes_content = File.read(routes_file)
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
# root 경로 설정 - welcome#index가 이미 있는지 확인
|
|
224
224
|
unless routes_content.include?("welcome#index")
|
|
225
225
|
if routes_content.match?(/^\s*root\s+/)
|
|
@@ -229,18 +229,18 @@ module Tayo
|
|
|
229
229
|
# root 설정이 없으면 추가
|
|
230
230
|
routes_content.gsub!(/Rails\.application\.routes\.draw do\s*\n/, "Rails.application.routes.draw do\n root 'welcome#index'\n")
|
|
231
231
|
end
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
File.write(routes_file, routes_content)
|
|
234
234
|
puts " ✅ routes.rb에 root 경로를 설정했습니다.".colorize(:green)
|
|
235
235
|
else
|
|
236
236
|
puts " ℹ️ routes.rb에 welcome#index가 이미 설정되어 있습니다.".colorize(:yellow)
|
|
237
237
|
end
|
|
238
|
-
|
|
238
|
+
|
|
239
239
|
puts "✅ Welcome 페이지가 생성되었습니다!".colorize(:green)
|
|
240
240
|
puts " 경로: /".colorize(:gray)
|
|
241
241
|
puts " 컨트롤러: app/controllers/welcome_controller.rb".colorize(:gray)
|
|
242
242
|
puts " 뷰: app/views/welcome/index.html.erb".colorize(:gray)
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
@welcome_page_created = true
|
|
245
245
|
end
|
|
246
246
|
|
|
@@ -250,20 +250,20 @@ module Tayo
|
|
|
250
250
|
puts "⚠️ Git 저장소가 아닙니다. 커밋을 건너뜁니다.".colorize(:yellow)
|
|
251
251
|
return
|
|
252
252
|
end
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
puts "📝 초기 상태를 Git에 커밋합니다...".colorize(:yellow)
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
# Git 상태 확인
|
|
257
257
|
git_status = `git status --porcelain`
|
|
258
|
-
|
|
258
|
+
|
|
259
259
|
if git_status.strip.empty?
|
|
260
260
|
puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:yellow)
|
|
261
261
|
return
|
|
262
262
|
end
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
# 변경사항 스테이징
|
|
265
265
|
system("git add .")
|
|
266
|
-
|
|
266
|
+
|
|
267
267
|
# 커밋
|
|
268
268
|
commit_message = "Save current state before Tayo initialization"
|
|
269
269
|
if system("git commit -m '#{commit_message}'")
|
|
@@ -280,20 +280,20 @@ module Tayo
|
|
|
280
280
|
puts "⚠️ Git 저장소가 아닙니다. 커밋을 건너뜁니다.".colorize(:yellow)
|
|
281
281
|
return
|
|
282
282
|
end
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
puts "📝 Tayo 설정 완료 상태를 Git에 커밋합니다...".colorize(:yellow)
|
|
285
|
-
|
|
285
|
+
|
|
286
286
|
# Git 상태 확인
|
|
287
287
|
git_status = `git status --porcelain`
|
|
288
|
-
|
|
288
|
+
|
|
289
289
|
if git_status.strip.empty?
|
|
290
290
|
puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:yellow)
|
|
291
291
|
return
|
|
292
292
|
end
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
# 변경사항 스테이징
|
|
295
295
|
system("git add .")
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
# 커밋
|
|
298
298
|
commit_message = "Complete Tayo initialization with Welcome page and Docker setup"
|
|
299
299
|
if system("git commit -m '#{commit_message}'")
|
|
@@ -306,14 +306,14 @@ module Tayo
|
|
|
306
306
|
|
|
307
307
|
def clear_docker_cache
|
|
308
308
|
puts "🧹 Docker 캐시를 정리합니다...".colorize(:yellow)
|
|
309
|
-
|
|
309
|
+
|
|
310
310
|
# Docker system prune
|
|
311
311
|
if system("docker system prune -f > /dev/null 2>&1")
|
|
312
312
|
puts "✅ Docker 시스템 캐시가 정리되었습니다.".colorize(:green)
|
|
313
313
|
else
|
|
314
314
|
puts "⚠️ Docker 시스템 정리에 실패했습니다.".colorize(:yellow)
|
|
315
315
|
end
|
|
316
|
-
|
|
316
|
+
|
|
317
317
|
# Kamal build cache clear
|
|
318
318
|
if File.exist?("config/deploy.yml")
|
|
319
319
|
puts "🚢 Kamal 빌드 캐시를 정리합니다...".colorize(:yellow)
|