tayo 0.1.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 +7 -0
- data/.DS_Store +0 -0
- data/README.md +100 -0
- data/Rakefile +4 -0
- data/exe/tayo +5 -0
- data/lib/tayo/cli.rb +31 -0
- data/lib/tayo/commands/cf.rb +390 -0
- data/lib/tayo/commands/gh.rb +389 -0
- data/lib/tayo/commands/init.rb +374 -0
- data/lib/tayo/version.rb +5 -0
- data/lib/tayo.rb +8 -0
- data/pkg/homebody-0.1.0.gem +0 -0
- data/sig/tayo.rbs +4 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 29025c061dcf5f809269fcb2e1af0435a7d24ac28bf21062f4ab065b7b4a17ff
|
4
|
+
data.tar.gz: 6e6f64d2ec7345b8966256a75ead710ce3e479a3e2f05e3624a66936b6c9464f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d29bbf7c0b505c53d777bda3262ccaeb14f1ba195a2c21e65cac50e5875862edaf8cb323e16c59ca56f4801e23984a6c0680ec7a4f9c4a478bb941f5fd4bea59
|
7
|
+
data.tar.gz: 5b664d65d34f5b9e3b258025750e0ad863660dab0bc472447fad605db92c340af16d4a7e55817ccbd2703bf5563d00c75d620f37c9267b15af77b5be137877ee
|
data/.DS_Store
ADDED
Binary file
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# Tayo
|
2
|
+
|
3
|
+
Rails 애플리케이션을 홈서버에 배포하기 위한 도구입니다.
|
4
|
+
|
5
|
+
## 설치
|
6
|
+
|
7
|
+
시스템 와이드로 설치:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
gem install tayo
|
11
|
+
```
|
12
|
+
|
13
|
+
## 사용법
|
14
|
+
|
15
|
+
### 1. `tayo init` - Rails 프로젝트 초기화
|
16
|
+
|
17
|
+
Rails 프로젝트를 홈서버 배포를 위해 준비합니다.
|
18
|
+
|
19
|
+
```bash
|
20
|
+
tayo init
|
21
|
+
```
|
22
|
+
|
23
|
+
이 명령어는 다음 작업들을 수행합니다:
|
24
|
+
|
25
|
+
- **OrbStack 설치 확인**: Docker 컨테이너를 실행하기 위한 OrbStack이 설치되어 있는지 확인합니다
|
26
|
+
- **Gemfile 수정**: development 그룹에 tayo gem을 추가합니다
|
27
|
+
- **Bundle 설치**: 의존성을 설치합니다
|
28
|
+
- **Linux 플랫폼 추가**: `x86_64-linux`와 `aarch64-linux` 플랫폼을 Gemfile.lock에 추가합니다
|
29
|
+
- **Dockerfile 생성**: Rails 7 기본 Dockerfile이 없으면 생성합니다
|
30
|
+
- **Welcome 페이지 생성**:
|
31
|
+
- `app/controllers/welcome_controller.rb` 컨트롤러 생성
|
32
|
+
- `app/views/welcome/index.html.erb` 뷰 파일 생성 (애니메이션이 있는 예쁜 랜딩 페이지)
|
33
|
+
- `config/routes.rb`에 `root 'welcome#index'` 설정 추가
|
34
|
+
- **Git 커밋**: 변경사항을 자동으로 커밋합니다
|
35
|
+
- **Docker 캐시 정리**: 디스크 공간 확보를 위해 Docker 캐시를 정리합니다
|
36
|
+
|
37
|
+
### 2. `tayo gh` - GitHub 저장소 및 Container Registry 설정
|
38
|
+
|
39
|
+
GitHub 저장소를 생성하고 Container Registry를 설정합니다.
|
40
|
+
|
41
|
+
```bash
|
42
|
+
tayo gh
|
43
|
+
```
|
44
|
+
|
45
|
+
이 명령어는 다음 작업들을 수행합니다:
|
46
|
+
|
47
|
+
- **GitHub CLI 설치 확인**: `gh` 명령어가 설치되어 있는지 확인합니다
|
48
|
+
- **GitHub 인증 확인**:
|
49
|
+
- GitHub에 로그인되어 있는지 확인
|
50
|
+
- 필요한 권한(repo, read:org, write:packages) 확인
|
51
|
+
- 권한이 없으면 브라우저에서 토큰 생성 페이지를 엽니다
|
52
|
+
- **Git 저장소 초기화**: 아직 git 저장소가 아니면 초기화합니다
|
53
|
+
- **GitHub 원격 저장소 설정**:
|
54
|
+
- 기존 원격 저장소가 있으면 사용
|
55
|
+
- 없으면 새 저장소 생성 (public/private 선택 가능)
|
56
|
+
- 코드를 GitHub에 푸시
|
57
|
+
- **GitHub Container Registry 설정**:
|
58
|
+
- Registry URL 생성: `ghcr.io/username/repository-name`
|
59
|
+
- Docker로 자동 로그인 실행
|
60
|
+
- **배포 설정 파일 생성**:
|
61
|
+
- `config/deploy.yml` 파일 생성 또는 업데이트
|
62
|
+
- 서버 IP, 도메인, 데이터베이스 등 설정 포함
|
63
|
+
- **환경 변수 파일 준비**:
|
64
|
+
- `.env.production` 파일 생성
|
65
|
+
- `.gitignore`에 추가하여 보안 유지
|
66
|
+
|
67
|
+
### 3. `tayo cf` - Cloudflare DNS 설정
|
68
|
+
|
69
|
+
Cloudflare를 통해 도메인을 홈서버 IP에 연결합니다.
|
70
|
+
|
71
|
+
```bash
|
72
|
+
tayo cf
|
73
|
+
```
|
74
|
+
|
75
|
+
이 명령어는 다음 작업들을 수행합니다:
|
76
|
+
|
77
|
+
- **설정 파일 확인**: `config/deploy.yml` 파일에서 서버 IP와 도메인 정보를 읽습니다
|
78
|
+
- **Cloudflare 인증**:
|
79
|
+
- API 토큰 입력 요청 (처음 실행 시)
|
80
|
+
- 토큰을 안전하게 저장 (macOS Keychain 사용)
|
81
|
+
- **도메인 Zone 확인**:
|
82
|
+
- Cloudflare 계정에서 도메인을 찾습니다
|
83
|
+
- Zone ID를 자동으로 가져옵니다
|
84
|
+
- **DNS 레코드 생성/업데이트**:
|
85
|
+
- A 레코드 생성: 도메인을 서버 IP에 연결
|
86
|
+
- 기존 레코드가 있으면 업데이트
|
87
|
+
- Proxied 설정 (Cloudflare CDN 사용)
|
88
|
+
- **설정 완료 확인**:
|
89
|
+
- DNS 설정이 완료되면 성공 메시지 표시
|
90
|
+
- 도메인으로 접속 가능함을 안내
|
91
|
+
|
92
|
+
각 명령어는 단계별로 진행 상황을 표시하며, 오류가 발생하면 친절한 안내 메시지를 제공합니다.
|
93
|
+
|
94
|
+
rails new 로 프로젝트 생성 후
|
95
|
+
bundle exec tayo init
|
96
|
+
bundle exec tayo gh
|
97
|
+
bundle exec tayo cf
|
98
|
+
|
99
|
+
순으로 진행 후
|
100
|
+
bin/kamal setup 으로 배포 진행
|
data/Rakefile
ADDED
data/exe/tayo
ADDED
data/lib/tayo/cli.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
require "colorize"
|
5
|
+
require_relative "commands/init"
|
6
|
+
require_relative "commands/gh"
|
7
|
+
require_relative "commands/cf"
|
8
|
+
|
9
|
+
module Tayo
|
10
|
+
class CLI < Thor
|
11
|
+
desc "init", "Rails 프로젝트에 Tayo를 설정합니다"
|
12
|
+
def init
|
13
|
+
Commands::Init.new.execute
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "gh", "GitHub 저장소와 컨테이너 레지스트리를 설정합니다"
|
17
|
+
def gh
|
18
|
+
Commands::Gh.new.execute
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "cf", "Cloudflare DNS를 설정하여 홈서버에 도메인을 연결합니다"
|
22
|
+
def cf
|
23
|
+
Commands::Cf.new.execute
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "version", "Tayo 버전을 표시합니다"
|
27
|
+
def version
|
28
|
+
puts "Tayo #{VERSION}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,390 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "colorize"
|
4
|
+
require "tty-prompt"
|
5
|
+
require "net/http"
|
6
|
+
require "json"
|
7
|
+
require "uri"
|
8
|
+
|
9
|
+
module Tayo
|
10
|
+
module Commands
|
11
|
+
class Cf
|
12
|
+
def execute
|
13
|
+
puts "☁️ Cloudflare DNS 설정을 시작합니다...".colorize(:green)
|
14
|
+
|
15
|
+
unless rails_project?
|
16
|
+
puts "❌ Rails 프로젝트가 아닙니다. Rails 프로젝트 루트에서 실행해주세요.".colorize(:red)
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
# 1. 도메인 입력받기
|
21
|
+
domain_info = get_domain_input
|
22
|
+
|
23
|
+
# 2. Cloudflare 토큰 생성 페이지 열기 및 권한 안내
|
24
|
+
open_token_creation_page
|
25
|
+
|
26
|
+
# 3. 토큰 입력받기
|
27
|
+
token = get_cloudflare_token
|
28
|
+
|
29
|
+
# 4. Cloudflare API로 도메인 목록 조회 및 선택
|
30
|
+
selected_zone = select_cloudflare_zone(token)
|
31
|
+
|
32
|
+
# 5. 루트 도메인 레코드 확인
|
33
|
+
existing_records = check_existing_records(token, selected_zone, domain_info)
|
34
|
+
|
35
|
+
# 6. DNS 레코드 추가/수정
|
36
|
+
setup_dns_record(token, selected_zone, domain_info, existing_records)
|
37
|
+
|
38
|
+
# 7. config/deploy.yml 업데이트
|
39
|
+
update_deploy_config(domain_info)
|
40
|
+
|
41
|
+
puts "\n🎉 Cloudflare DNS 설정이 완료되었습니다!".colorize(:green)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def rails_project?
|
47
|
+
File.exist?("Gemfile") && File.exist?("config/application.rb")
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_domain_input
|
51
|
+
prompt = TTY::Prompt.new
|
52
|
+
|
53
|
+
puts "\n📝 배포할 도메인을 설정합니다.".colorize(:yellow)
|
54
|
+
|
55
|
+
domain = prompt.ask("배포할 도메인을 입력하세요 (예: myapp.com, api.example.com):") do |q|
|
56
|
+
q.validate(/\A[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/, "올바른 도메인 형식을 입력해주세요 (예: myapp.com)")
|
57
|
+
end
|
58
|
+
|
59
|
+
# 도메인이 루트인지 서브도메인인지 판단
|
60
|
+
parts = domain.split('.')
|
61
|
+
if parts.length == 2
|
62
|
+
{ type: :root, domain: domain, zone: domain }
|
63
|
+
else
|
64
|
+
zone = parts[-2..-1].join('.')
|
65
|
+
{ type: :subdomain, domain: domain, zone: zone, subdomain: parts[0..-3].join('.') }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def open_token_creation_page
|
70
|
+
puts "\n🔑 Cloudflare API 토큰이 필요합니다.".colorize(:yellow)
|
71
|
+
puts "토큰 생성 페이지를 엽니다...".colorize(:cyan)
|
72
|
+
|
73
|
+
# Cloudflare API 토큰 생성 페이지 열기
|
74
|
+
system("open 'https://dash.cloudflare.com/profile/api-tokens'")
|
75
|
+
|
76
|
+
puts "\n다음 권한으로 토큰을 생성해주세요:".colorize(:yellow)
|
77
|
+
puts ""
|
78
|
+
puts "한국어 화면:".colorize(:gray)
|
79
|
+
puts "• 영역 → DNS → 읽기".colorize(:white)
|
80
|
+
puts "• 영역 → DNS → 편집".colorize(:white)
|
81
|
+
puts " (영역 리소스는 '모든 영역' 선택)".colorize(:gray)
|
82
|
+
puts ""
|
83
|
+
puts "English:".colorize(:gray)
|
84
|
+
puts "• Zone → DNS → Read".colorize(:white)
|
85
|
+
puts "• Zone → DNS → Edit".colorize(:white)
|
86
|
+
puts " (Zone Resources: Select 'All zones')".colorize(:gray)
|
87
|
+
puts ""
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_cloudflare_token
|
91
|
+
prompt = TTY::Prompt.new
|
92
|
+
|
93
|
+
token = prompt.mask("생성된 Cloudflare API 토큰을 붙여넣으세요:")
|
94
|
+
|
95
|
+
if token.nil? || token.strip.empty?
|
96
|
+
puts "❌ 토큰이 입력되지 않았습니다.".colorize(:red)
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
|
100
|
+
# 토큰 유효성 간단 확인
|
101
|
+
if test_cloudflare_token(token.strip)
|
102
|
+
puts "✅ 토큰이 확인되었습니다.".colorize(:green)
|
103
|
+
return token.strip
|
104
|
+
else
|
105
|
+
puts "❌ 토큰이 올바르지 않거나 권한이 부족합니다.".colorize(:red)
|
106
|
+
exit 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_cloudflare_token(token)
|
111
|
+
uri = URI('https://api.cloudflare.com/client/v4/user/tokens/verify')
|
112
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
113
|
+
http.use_ssl = true
|
114
|
+
|
115
|
+
request = Net::HTTP::Get.new(uri)
|
116
|
+
request['Authorization'] = "Bearer #{token}"
|
117
|
+
request['Content-Type'] = 'application/json'
|
118
|
+
|
119
|
+
response = http.request(request)
|
120
|
+
return response.code == '200'
|
121
|
+
rescue
|
122
|
+
return false
|
123
|
+
end
|
124
|
+
|
125
|
+
def select_cloudflare_zone(token)
|
126
|
+
puts "\n🌐 Cloudflare 도메인 목록을 조회합니다...".colorize(:yellow)
|
127
|
+
|
128
|
+
zones = get_cloudflare_zones(token)
|
129
|
+
|
130
|
+
if zones.empty?
|
131
|
+
puts "❌ Cloudflare에 등록된 도메인이 없습니다.".colorize(:red)
|
132
|
+
puts "먼저 https://dash.cloudflare.com 에서 도메인을 추가해주세요.".colorize(:cyan)
|
133
|
+
exit 1
|
134
|
+
end
|
135
|
+
|
136
|
+
prompt = TTY::Prompt.new
|
137
|
+
zone_choices = zones.map { |zone| "#{zone['name']} (#{zone['status']})" }
|
138
|
+
|
139
|
+
selected = prompt.select("도메인을 선택하세요:", zone_choices)
|
140
|
+
zone_name = selected.split(' ').first
|
141
|
+
|
142
|
+
selected_zone = zones.find { |zone| zone['name'] == zone_name }
|
143
|
+
puts "✅ 선택된 도메인: #{zone_name}".colorize(:green)
|
144
|
+
|
145
|
+
return selected_zone
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_cloudflare_zones(token)
|
149
|
+
uri = URI('https://api.cloudflare.com/client/v4/zones')
|
150
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
151
|
+
http.use_ssl = true
|
152
|
+
|
153
|
+
request = Net::HTTP::Get.new(uri)
|
154
|
+
request['Authorization'] = "Bearer #{token}"
|
155
|
+
request['Content-Type'] = 'application/json'
|
156
|
+
|
157
|
+
response = http.request(request)
|
158
|
+
|
159
|
+
if response.code == '200'
|
160
|
+
data = JSON.parse(response.body)
|
161
|
+
return data['result'] || []
|
162
|
+
else
|
163
|
+
puts "❌ 도메인 목록 조회에 실패했습니다: #{response.code}".colorize(:red)
|
164
|
+
exit 1
|
165
|
+
end
|
166
|
+
rescue => e
|
167
|
+
puts "❌ API 요청 중 오류가 발생했습니다: #{e.message}".colorize(:red)
|
168
|
+
exit 1
|
169
|
+
end
|
170
|
+
|
171
|
+
def check_existing_records(token, zone, domain_info)
|
172
|
+
puts "\n🔍 기존 DNS 레코드를 확인합니다...".colorize(:yellow)
|
173
|
+
|
174
|
+
zone_id = zone['id']
|
175
|
+
zone_name = zone['name']
|
176
|
+
|
177
|
+
# 루트 도메인의 A/CNAME 레코드 확인
|
178
|
+
records = get_dns_records(token, zone_id, zone_name, ['A', 'CNAME'])
|
179
|
+
|
180
|
+
puts "기존 레코드: #{records.length}개 발견".colorize(:gray)
|
181
|
+
|
182
|
+
return records
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_dns_records(token, zone_id, name, types)
|
186
|
+
records = []
|
187
|
+
|
188
|
+
types.each do |type|
|
189
|
+
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
190
|
+
uri.query = URI.encode_www_form({
|
191
|
+
type: type,
|
192
|
+
name: name
|
193
|
+
})
|
194
|
+
|
195
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
196
|
+
http.use_ssl = true
|
197
|
+
|
198
|
+
request = Net::HTTP::Get.new(uri)
|
199
|
+
request['Authorization'] = "Bearer #{token}"
|
200
|
+
request['Content-Type'] = 'application/json'
|
201
|
+
|
202
|
+
response = http.request(request)
|
203
|
+
|
204
|
+
if response.code == '200'
|
205
|
+
data = JSON.parse(response.body)
|
206
|
+
records.concat(data['result'] || [])
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
return records
|
211
|
+
rescue => e
|
212
|
+
puts "❌ DNS 레코드 조회 중 오류: #{e.message}".colorize(:red)
|
213
|
+
return []
|
214
|
+
end
|
215
|
+
|
216
|
+
def setup_dns_record(token, zone, domain_info, existing_records)
|
217
|
+
puts "\n⚙️ DNS 레코드를 설정합니다...".colorize(:yellow)
|
218
|
+
|
219
|
+
# 홈서버 IP/URL 입력받기
|
220
|
+
prompt = TTY::Prompt.new
|
221
|
+
|
222
|
+
server_info = prompt.ask("홈서버 IP 또는 도메인을 입력하세요:") do |q|
|
223
|
+
q.validate(/\A.+\z/, "서버 정보를 입력해주세요")
|
224
|
+
end
|
225
|
+
|
226
|
+
# SSH 사용자 계정 입력받기
|
227
|
+
ssh_user = prompt.ask("SSH 사용자 계정을 입력하세요:", default: "root")
|
228
|
+
|
229
|
+
# IP인지 도메인인지 판단
|
230
|
+
is_ip = server_info.match?(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/)
|
231
|
+
record_type = is_ip ? 'A' : 'CNAME'
|
232
|
+
|
233
|
+
zone_id = zone['id']
|
234
|
+
zone_name = zone['name']
|
235
|
+
|
236
|
+
# 도메인 정보에 따라 레코드 설정
|
237
|
+
final_domain = determine_final_domain(domain_info, zone_name, existing_records)
|
238
|
+
|
239
|
+
# 대상 도메인의 모든 A/CNAME 레코드 확인
|
240
|
+
all_records = get_dns_records(token, zone_id, final_domain[:name], ['A', 'CNAME'])
|
241
|
+
|
242
|
+
if all_records.any?
|
243
|
+
existing_record = all_records.first
|
244
|
+
|
245
|
+
# 동일한 타입이고 같은 값이면 건너뛰기
|
246
|
+
if existing_record['type'] == record_type && existing_record['content'] == server_info
|
247
|
+
puts "✅ DNS 레코드가 이미 올바르게 설정되어 있습니다.".colorize(:green)
|
248
|
+
puts " #{final_domain[:full_domain]} → #{server_info} (#{record_type} 레코드)".colorize(:gray)
|
249
|
+
else
|
250
|
+
# 타입이 다르거나 값이 다른 경우 삭제 후 재생성
|
251
|
+
puts "⚠️ 기존 레코드를 삭제하고 새로 생성합니다.".colorize(:yellow)
|
252
|
+
puts " 기존: #{existing_record['content']} (#{existing_record['type']}) → 새로운: #{server_info} (#{record_type})".colorize(:gray)
|
253
|
+
|
254
|
+
# 기존 레코드 삭제
|
255
|
+
delete_dns_record(token, zone_id, existing_record['id'])
|
256
|
+
|
257
|
+
# 새 레코드 생성
|
258
|
+
create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
|
259
|
+
end
|
260
|
+
else
|
261
|
+
# DNS 레코드 생성
|
262
|
+
create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
|
263
|
+
end
|
264
|
+
|
265
|
+
# 최종 도메인 정보 저장
|
266
|
+
@final_domain = final_domain[:full_domain]
|
267
|
+
@server_info = server_info
|
268
|
+
@ssh_user = ssh_user
|
269
|
+
end
|
270
|
+
|
271
|
+
def determine_final_domain(domain_info, zone_name, existing_records)
|
272
|
+
case domain_info[:type]
|
273
|
+
when :root
|
274
|
+
if existing_records.any?
|
275
|
+
puts "⚠️ 루트 도메인에 이미 레코드가 있습니다. app.#{zone_name}을 사용합니다.".colorize(:yellow)
|
276
|
+
{ name: "app.#{zone_name}", full_domain: "app.#{zone_name}" }
|
277
|
+
else
|
278
|
+
{ name: zone_name, full_domain: zone_name }
|
279
|
+
end
|
280
|
+
when :subdomain
|
281
|
+
{ name: domain_info[:domain], full_domain: domain_info[:domain] }
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def create_dns_record(token, zone_id, name, type, content)
|
286
|
+
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
287
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
288
|
+
http.use_ssl = true
|
289
|
+
|
290
|
+
request = Net::HTTP::Post.new(uri)
|
291
|
+
request['Authorization'] = "Bearer #{token}"
|
292
|
+
request['Content-Type'] = 'application/json'
|
293
|
+
|
294
|
+
data = {
|
295
|
+
type: type,
|
296
|
+
name: name,
|
297
|
+
content: content,
|
298
|
+
ttl: 300
|
299
|
+
}
|
300
|
+
|
301
|
+
request.body = data.to_json
|
302
|
+
response = http.request(request)
|
303
|
+
|
304
|
+
if response.code == '200'
|
305
|
+
puts "✅ DNS 레코드가 생성되었습니다.".colorize(:green)
|
306
|
+
puts " #{name} → #{content} (#{type} 레코드)".colorize(:gray)
|
307
|
+
else
|
308
|
+
puts "❌ DNS 레코드 생성에 실패했습니다: #{response.code}".colorize(:red)
|
309
|
+
puts response.body
|
310
|
+
exit 1
|
311
|
+
end
|
312
|
+
rescue => e
|
313
|
+
puts "❌ DNS 레코드 생성 중 오류: #{e.message}".colorize(:red)
|
314
|
+
exit 1
|
315
|
+
end
|
316
|
+
|
317
|
+
def delete_dns_record(token, zone_id, record_id)
|
318
|
+
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records/#{record_id}")
|
319
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
320
|
+
http.use_ssl = true
|
321
|
+
|
322
|
+
request = Net::HTTP::Delete.new(uri)
|
323
|
+
request['Authorization'] = "Bearer #{token}"
|
324
|
+
request['Content-Type'] = 'application/json'
|
325
|
+
|
326
|
+
response = http.request(request)
|
327
|
+
|
328
|
+
if response.code == '200'
|
329
|
+
puts "✅ 기존 DNS 레코드가 삭제되었습니다.".colorize(:green)
|
330
|
+
else
|
331
|
+
puts "❌ DNS 레코드 삭제에 실패했습니다: #{response.code}".colorize(:red)
|
332
|
+
puts response.body
|
333
|
+
exit 1
|
334
|
+
end
|
335
|
+
rescue => e
|
336
|
+
puts "❌ DNS 레코드 삭제 중 오류: #{e.message}".colorize(:red)
|
337
|
+
exit 1
|
338
|
+
end
|
339
|
+
|
340
|
+
def update_deploy_config(domain_info)
|
341
|
+
puts "\n📝 배포 설정을 업데이트합니다...".colorize(:yellow)
|
342
|
+
|
343
|
+
config_file = "config/deploy.yml"
|
344
|
+
|
345
|
+
unless File.exist?(config_file)
|
346
|
+
puts "⚠️ config/deploy.yml 파일이 없습니다.".colorize(:yellow)
|
347
|
+
return
|
348
|
+
end
|
349
|
+
|
350
|
+
content = File.read(config_file)
|
351
|
+
|
352
|
+
# proxy.host 설정 업데이트
|
353
|
+
if content.include?("proxy:")
|
354
|
+
content.gsub!(/(\s+host:\s+).*$/, "\\1#{@final_domain}")
|
355
|
+
else
|
356
|
+
# proxy 섹션이 없으면 추가
|
357
|
+
proxy_config = "\n# Proxy configuration\nproxy:\n ssl: true\n host: #{@final_domain}\n"
|
358
|
+
content += proxy_config
|
359
|
+
end
|
360
|
+
|
361
|
+
# servers 설정 업데이트
|
362
|
+
if content.match?(/servers:\s*\n\s*web:\s*\n\s*-\s*/)
|
363
|
+
content.gsub!(/(\s*servers:\s*\n\s*web:\s*\n\s*-\s*)[\d.]+/, "\\1#{@server_info}")
|
364
|
+
end
|
365
|
+
|
366
|
+
# ssh user 설정 업데이트
|
367
|
+
if @ssh_user && @ssh_user != "root"
|
368
|
+
if content.match?(/^ssh:/)
|
369
|
+
# 기존 ssh 섹션 업데이트
|
370
|
+
content.gsub!(/^ssh:\s*\n\s*user:\s*\w+/, "ssh:\n user: #{@ssh_user}")
|
371
|
+
else
|
372
|
+
# ssh 섹션 추가 (accessories 섹션 앞에 추가)
|
373
|
+
if content.match?(/^# Use accessory services/)
|
374
|
+
content.gsub!(/^# Use accessory services/, "# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n\n# Use accessory services")
|
375
|
+
else
|
376
|
+
# 파일 끝에 추가
|
377
|
+
content += "\n# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
File.write(config_file, content)
|
383
|
+
puts "✅ config/deploy.yml이 업데이트되었습니다.".colorize(:green)
|
384
|
+
puts " proxy.host: #{@final_domain}".colorize(:gray)
|
385
|
+
puts " servers.web: #{@server_info}".colorize(:gray)
|
386
|
+
puts " ssh.user: #{@ssh_user}".colorize(:gray) if @ssh_user && @ssh_user != "root"
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|