tayo 0.1.11 → 0.1.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b925f8aa2d0555113bba7ca0f1ed1e2a4f7b923f4f8cd5f7ebc2ed0abb0a5df
4
- data.tar.gz: 42fe13a69f4c8b9bca65b2d46f8bfe501e34f2c350658d75e277dc1fb2005268
3
+ metadata.gz: 2a5fae5e5027e6af704d0de853a7402c0011dbe9e4e9638bd8e05553a60d9f13
4
+ data.tar.gz: 7b0446e1db678ae717d49176d415095820622e19b3f75812182090a9ff32aa89
5
5
  SHA512:
6
- metadata.gz: 13ce503cf8c8d0fd7c0ec6588cbf699d0713c01e6b1d9c26fa1b7c0339c4c1747b93b959e5e5071e9213ec28d6e2aa634032511f4ae921f5fbfeb2fd4ab5bac8
7
- data.tar.gz: d131a5928299a44bf63b2a0e1c65ebcf12113643120fc5d2f5a2084643b7e05de9388e34575ffddc3ad1e1804268051ce4fdd8df5213609b5e0361a6f447b304
6
+ metadata.gz: 5b9c66d762054c1df3a9a40d1a0f98e29eb2428aa43dd048ad678ce85461f2f81f1486bd92e2e67b7b7c1d34ccfe2818a59bea4bfd02972a2948f9ee55f4597f
7
+ data.tar.gz: 6df1da4eeaa3def9471469d100ed1dd8575129eb1ac959a2915d45a20da0f3e7f97f3d893d7ac46965207827ff0febd73a55732ac3538a0dd9156ccd30e4d7bf
data/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # 변경 기록 (Changelog)
2
+
3
+ ## [0.1.12] - 2025-01-20
4
+
5
+ ### 🚀 새로운 기능
6
+ - **CLAUDE.md 문서 지원**: Claude AI 도우미를 위한 프로젝트별 지침 파일 지원
7
+ - **Cloudflare DNS 설정 대폭 개선**:
8
+ - Cloudflare Zone 목록에서 도메인을 직접 선택하는 방식으로 변경
9
+ - 루트 도메인(@)과 서브도메인 선택 UI 개선
10
+ - 기존 DNS 레코드 확인 및 덮어쓰기 시 사용자 확인 프롬프트 추가
11
+ - Cloudflare API 토큰을 `~/.tayo` 파일에 안전하게 저장 및 재사용
12
+ - **자동 Git 커밋**: `tayo gh`와 `tayo cf` 명령어 실행 후 자동으로 변경사항 커밋
13
+ - **버전 표시**: 명령어 실행 시 Tayo 버전 표시
14
+
15
+ ### 🛠️ 개선사항
16
+ - **Init 워크플로우 간소화**:
17
+ - Gemfile 수정 제거로 더 깔끔한 초기화 프로세스
18
+ - Docker 캐시 정리 기능 추가
19
+ - bootsnap 자동 처리 제거 (안정성 문제로 인해)
20
+ - **GitHub Container Registry 설정**:
21
+ - 조직(Organization) 계정 지원 추가
22
+ - ghcr.io URL 중복 제거 버그 수정
23
+ - Docker 로그인 자동화
24
+
25
+ ### 🐛 버그 수정
26
+ - Dockerfile bootsnap 프리컴파일 이슈 수정
27
+ - DNS 레코드 생성 시 프록시 설정 누락 문제 해결
28
+
29
+ ## [0.1.11] - 2025-01-19
30
+
31
+ ### 🛠️ 개선사항
32
+ - Bootsnap 처리를 DockerfileModifier 클래스로 리팩토링
33
+ - 더 안정적인 bootsnap 라인 제거 로직 구현
34
+ - 테스트 커버리지 강화
35
+
36
+ ## [0.1.10] - 2025-01-18
37
+
38
+ ### 🚀 새로운 기능
39
+ - Dockerfile에서 bootsnap 관련 설정 자동 제거/비활성화
40
+
41
+ ### 🐛 버그 수정
42
+ - Dockerfile bootsnap 프리컴파일 관련 다양한 엣지 케이스 처리
43
+
44
+ ## [0.1.9] - 2025-01-17
45
+
46
+ ### 🏗️ 기타 변경사항
47
+ - 홈페이지 URL을 TeamMilestone 조직으로 업데이트
48
+ - 저장소 이전에 따른 메타데이터 업데이트
49
+
50
+ ## [0.1.0] - 2025-01-15
51
+
52
+ ### 🎉 최초 릴리스
53
+ - `tayo init`: Rails 프로젝트 초기 설정 (Docker, Welcome 페이지)
54
+ - `tayo gh`: GitHub 저장소 및 Container Registry 설정
55
+ - `tayo cf`: Cloudflare DNS 설정
56
+ - OrbStack 자동 감지 및 실행
57
+ - 한국어 UI 지원
data/CLAUDE.md ADDED
@@ -0,0 +1,58 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+ Tayo is a Ruby gem that simplifies Rails app deployment to home servers using GitHub Container Registry and Cloudflare.
7
+
8
+ ## Common Development Commands
9
+
10
+ ### Testing
11
+ ```bash
12
+ # Run all tests
13
+ rake test
14
+
15
+ # Run specific test file
16
+ ruby -Ilib:test test/dockerfile_modifier_test.rb
17
+ ```
18
+
19
+ ### Building and Installing
20
+ ```bash
21
+ # Build gem
22
+ rake build
23
+
24
+ # Install locally
25
+ rake install
26
+
27
+ # Release to RubyGems.org
28
+ rake release
29
+ ```
30
+
31
+ ## Architecture
32
+
33
+ ### Core Structure
34
+ - `lib/tayo/cli.rb` - Thor-based CLI entry point
35
+ - `lib/tayo/commands/` - Command modules:
36
+ - `init.rb` - Rails project initialization with Docker setup
37
+ - `gh.rb` - GitHub repository and Container Registry configuration
38
+ - `cf.rb` - Cloudflare DNS configuration
39
+ - `lib/tayo/dockerfile_modifier.rb` - Handles bootsnap removal from Dockerfiles
40
+
41
+ ### Key Patterns
42
+ 1. **Command Structure**: Each command is a separate module under `Commands`
43
+ 2. **Error Handling**: Use colorized Korean messages for user-friendly output
44
+ 3. **Security**: Store sensitive tokens with 600 permissions, use macOS Keychain for Cloudflare tokens
45
+ 4. **Git Integration**: Auto-commit after each major step with descriptive messages
46
+ 5. **User Interaction**: Use TTY::Prompt for interactive configuration
47
+
48
+ ### Workflow
49
+ The typical usage flow:
50
+ 1. `tayo init` - Sets up Rails project with Docker
51
+ 2. `tayo gh` - Configures GitHub repository and Container Registry
52
+ 3. `tayo cf` - Sets up Cloudflare DNS
53
+ 4. `bin/kamal setup` - Deploys the application
54
+
55
+ ### Testing Approach
56
+ - Uses Minitest framework
57
+ - Tests focus on unit testing individual components
58
+ - DockerfileModifier has comprehensive test coverage
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Tayo
2
2
 
3
- Rails 애플리케이션을 홈서버에 배포하기 위한 도구입니다.
3
+ Rails 애플리케이션을 홈서버에 배포하기 위한 도구입니다. GitHub Container Registry와 Cloudflare를 활용하여 간편한 배포 워크플로우를 제공합니다.
4
4
 
5
5
  ## 설치
6
6
 
@@ -23,15 +23,13 @@ tayo init
23
23
  이 명령어는 다음 작업들을 수행합니다:
24
24
 
25
25
  - **OrbStack 설치 확인**: Docker 컨테이너를 실행하기 위한 OrbStack이 설치되어 있는지 확인합니다
26
- - **Bundle 설치**: 의존성을 설치합니다
27
- - **Linux 플랫폼 추가**: `x86_64-linux`와 `aarch64-linux` 플랫폼을 Gemfile.lock에 추가합니다
28
26
  - **Dockerfile 생성**: Rails 7 기본 Dockerfile이 없으면 생성합니다
29
27
  - **Welcome 페이지 생성**:
30
28
  - `app/controllers/welcome_controller.rb` 컨트롤러 생성
31
29
  - `app/views/welcome/index.html.erb` 뷰 파일 생성 (애니메이션이 있는 예쁜 랜딩 페이지)
32
30
  - `config/routes.rb`에 `root 'welcome#index'` 설정 추가
33
- - **Git 커밋**: 변경사항을 자동으로 커밋합니다
34
31
  - **Docker 캐시 정리**: 디스크 공간 확보를 위해 Docker 캐시를 정리합니다
32
+ - **Git 커밋**: 변경사항을 자동으로 커밋합니다
35
33
 
36
34
  ### 2. `tayo gh` - GitHub 저장소 및 Container Registry 설정
37
35
 
@@ -52,6 +50,7 @@ tayo gh
52
50
  - **GitHub 원격 저장소 설정**:
53
51
  - 기존 원격 저장소가 있으면 사용
54
52
  - 없으면 새 저장소 생성 (public/private 선택 가능)
53
+ - 개인 계정 및 조직(Organization) 계정 모두 지원
55
54
  - 코드를 GitHub에 푸시
56
55
  - **GitHub Container Registry 설정**:
57
56
  - Registry URL 생성: `ghcr.io/username/repository-name`
@@ -62,6 +61,7 @@ tayo gh
62
61
  - **환경 변수 파일 준비**:
63
62
  - `.env.production` 파일 생성
64
63
  - `.gitignore`에 추가하여 보안 유지
64
+ - **Git 커밋**: 설정 변경사항을 자동으로 커밋합니다
65
65
 
66
66
  ### 3. `tayo cf` - Cloudflare DNS 설정
67
67
 
@@ -73,27 +73,57 @@ tayo cf
73
73
 
74
74
  이 명령어는 다음 작업들을 수행합니다:
75
75
 
76
- - **설정 파일 확인**: `config/deploy.yml` 파일에서 서버 IP와 도메인 정보를 읽습니다
77
76
  - **Cloudflare 인증**:
78
77
  - API 토큰 입력 요청 (처음 실행 시)
79
- - 토큰을 안전하게 저장 (macOS Keychain 사용)
80
- - **도메인 Zone 확인**:
81
- - Cloudflare 계정에서 도메인을 찾습니다
82
- - Zone ID를 자동으로 가져옵니다
78
+ - 토큰을 `~/.tayo` 파일에 안전하게 저장하여 재사용
79
+ - 필요한 권한: Zone:Read, DNS:Edit
80
+ - **도메인 설정**:
81
+ - Cloudflare 계정의 Zone 목록에서 도메인 선택
82
+ - 루트 도메인(@) 또는 서브도메인 설정 지원
83
+ - 대화형 UI로 쉽게 설정 가능
83
84
  - **DNS 레코드 생성/업데이트**:
84
- - A 레코드 생성: 도메인을 서버 IP에 연결
85
- - 기존 레코드가 있으면 업데이트
85
+ - A 레코드 생성: IP 주소로 연결
86
+ - CNAME 레코드 생성: 도메인으로 연결
87
+ - 기존 레코드가 있으면 사용자 확인 후 업데이트
86
88
  - Proxied 설정 (Cloudflare CDN 사용)
87
- - **설정 완료 확인**:
88
- - DNS 설정이 완료되면 성공 메시지 표시
89
- - 도메인으로 접속 가능함을 안내
89
+ - **배포 설정 업데이트**:
90
+ - `config/deploy.yml` 파일의 proxy.host 자동 업데이트
91
+ - 서버 정보 SSH 사용자 설정
92
+ - **Git 커밋**: DNS 설정 변경사항을 자동으로 커밋합니다
93
+
94
+ ## 전체 워크플로우
95
+
96
+ ```bash
97
+ # 1. 새 Rails 프로젝트 생성
98
+ rails new myapp
99
+ cd myapp
100
+
101
+ # 2. Tayo로 배포 준비
102
+ tayo init # Rails 프로젝트 초기화
103
+ tayo gh # GitHub 저장소 및 Container Registry 설정
104
+ tayo cf # Cloudflare DNS 설정
105
+
106
+ # 3. Kamal로 배포
107
+ bin/kamal setup
108
+ ```
109
+
110
+ ## 주요 기능
111
+
112
+ - **🚀 원스톱 배포 설정**: 3개의 명령어로 배포 준비 완료
113
+ - **🐳 Docker 기반**: OrbStack과 GitHub Container Registry 활용
114
+ - **🌐 Cloudflare 통합**: 자동 DNS 설정 및 CDN 지원
115
+ - **🔒 보안**: 토큰과 환경 변수를 안전하게 관리
116
+ - **🎯 한국어 UI**: 모든 메시지가 한국어로 제공
117
+ - **🛡️ 오류 처리**: 각 단계별 검증과 친절한 오류 메시지
118
+
119
+ ## 요구사항
90
120
 
91
- 명령어는 단계별로 진행 상황을 표시하며, 오류가 발생하면 친절한 안내 메시지를 제공합니다.
121
+ - Ruby 3.1.0 이상
122
+ - Rails 7.0 이상
123
+ - macOS (OrbStack 사용)
124
+ - GitHub 계정
125
+ - Cloudflare 계정
92
126
 
93
- rails new 로 프로젝트 생성 후
94
- bundle exec tayo init
95
- bundle exec tayo gh
96
- bundle exec tayo cf
127
+ ## 라이선스
97
128
 
98
- 순으로 진행 후
99
- bin/kamal setup 으로 배포 진행
129
+ MIT License
@@ -17,30 +17,27 @@ module Tayo
17
17
  return
18
18
  end
19
19
 
20
- # 1. 도메인 입력받기
21
- domain_info = get_domain_input
20
+ # --- 로직 순서 변경 ---
22
21
 
23
- # 2. Cloudflare 토큰 생성 페이지 열기 및 권한 안내
24
- open_token_creation_page
25
-
26
- # 3. 토큰 입력받기
22
+ # 2. 토큰 입력받기
27
23
  token = get_cloudflare_token
28
24
 
29
- # 4. Cloudflare API로 도메인 목록 조회 및 선택
30
- selected_zone = select_cloudflare_zone(token)
31
-
32
- # 5. 루트 도메인 레코드 확인
25
+ # 3. Cloudflare 선택 및 도메인 구성 (새로운 방식)
26
+ domain_info = configure_domain_from_zones(token)
27
+ selected_zone = domain_info[:selected_zone_object]
28
+
29
+ # 4. 기존 DNS 레코드 확인 (참고용)
33
30
  existing_records = check_existing_records(token, selected_zone, domain_info)
34
31
 
35
- # 6. DNS 레코드 추가/수정
32
+ # 5. DNS 레코드 추가/수정 (루트 도메인 덮어쓰기 로직 포함)
36
33
  setup_dns_record(token, selected_zone, domain_info, existing_records)
37
34
 
38
- # 7. config/deploy.yml 업데이트
35
+ # 6. config/deploy.yml 업데이트
39
36
  update_deploy_config(domain_info)
40
37
 
41
38
  puts "\n🎉 Cloudflare DNS 설정이 완료되었습니다!".colorize(:green)
42
39
 
43
- # 변경사항 커밋
40
+ # 7. 변경사항 커밋
44
41
  commit_cloudflare_changes(domain_info)
45
42
  end
46
43
 
@@ -50,31 +47,59 @@ module Tayo
50
47
  File.exist?("Gemfile") && File.exist?("config/application.rb")
51
48
  end
52
49
 
53
- def get_domain_input
54
- prompt = TTY::Prompt.new
50
+ # [신규] Cloudflare Zone 목록에서 도메인을 선택하고 구성하는 메소드
51
+ def configure_domain_from_zones(token)
52
+ puts "\n🌐 Cloudflare 계정의 도메인 목록을 조회합니다...".colorize(:yellow)
55
53
 
56
- puts "\n📝 배포할 도메인을 설정합니다.".colorize(:yellow)
54
+ zones = get_cloudflare_zones(token)
57
55
 
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)")
56
+ if zones.empty?
57
+ puts " Cloudflare에 등록된 도메인(Zone)이 없습니다.".colorize(:red)
58
+ puts "먼저 https://dash.cloudflare.com 에서 도메인을 추가해주세요.".colorize(:cyan)
59
+ exit 1
60
60
  end
61
61
 
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('.') }
62
+ 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
+
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
+ }
69
98
  end
70
99
  end
71
100
 
72
101
  def open_token_creation_page
73
102
  puts "\n🔑 Cloudflare API 토큰이 필요합니다.".colorize(:yellow)
74
- puts "토큰 생성 페이지를 엽니다...".colorize(:cyan)
75
-
76
- # Cloudflare API 토큰 생성 페이지 열기
77
- system("open 'https://dash.cloudflare.com/profile/api-tokens'")
78
103
 
79
104
  puts "\n다음 권한으로 토큰을 생성해주세요:".colorize(:yellow)
80
105
  puts ""
@@ -88,9 +113,28 @@ module Tayo
88
113
  puts "• Zone → DNS → Edit".colorize(:white)
89
114
  puts " (Zone Resources: Select 'All zones')".colorize(:gray)
90
115
  puts ""
116
+
117
+ puts "토큰 생성 페이지를 엽니다...".colorize(:cyan)
118
+
119
+ system("open 'https://dash.cloudflare.com/profile/api-tokens'")
91
120
  end
92
121
 
93
122
  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
+
94
138
  prompt = TTY::Prompt.new
95
139
 
96
140
  token = prompt.mask("생성된 Cloudflare API 토큰을 붙여넣으세요:")
@@ -100,9 +144,9 @@ module Tayo
100
144
  exit 1
101
145
  end
102
146
 
103
- # 토큰 유효성 간단 확인
104
147
  if test_cloudflare_token(token.strip)
105
148
  puts "✅ 토큰이 확인되었습니다.".colorize(:green)
149
+ save_token(token.strip)
106
150
  return token.strip
107
151
  else
108
152
  puts "❌ 토큰이 올바르지 않거나 권한이 부족합니다.".colorize(:red)
@@ -125,27 +169,42 @@ module Tayo
125
169
  return false
126
170
  end
127
171
 
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
172
+ def load_saved_token
173
+ token_file = File.expand_path("~/.tayo")
174
+ return nil unless File.exist?(token_file)
175
+
176
+ begin
177
+ content = File.read(token_file)
178
+ token_line = content.lines.find { |line| line.start_with?("CLOUDFLARE_TOKEN=") }
179
+ return nil unless token_line
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)
137
207
  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
149
208
  end
150
209
 
151
210
  def get_cloudflare_zones(token)
@@ -174,13 +233,10 @@ module Tayo
174
233
  def check_existing_records(token, zone, domain_info)
175
234
  puts "\n🔍 기존 DNS 레코드를 확인합니다...".colorize(:yellow)
176
235
 
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'])
236
+ target_name = (domain_info[:type] == :root) ? zone['name'] : domain_info[:domain]
237
+ records = get_dns_records(token, zone['id'], target_name, ['A', 'CNAME'])
182
238
 
183
- puts "기존 레코드: #{records.length}개 발견".colorize(:gray)
239
+ puts " (확인 대상: #{target_name}, 발견된 A/CNAME 레코드: #{records.length}개)".colorize(:gray)
184
240
 
185
241
  return records
186
242
  end
@@ -190,10 +246,7 @@ module Tayo
190
246
 
191
247
  types.each do |type|
192
248
  uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
193
- uri.query = URI.encode_www_form({
194
- type: type,
195
- name: name
196
- })
249
+ uri.query = URI.encode_www_form({ type: type, name: name })
197
250
 
198
251
  http = Net::HTTP.new(uri.host, uri.port)
199
252
  http.use_ssl = true
@@ -215,71 +268,74 @@ module Tayo
215
268
  puts "❌ DNS 레코드 조회 중 오류: #{e.message}".colorize(:red)
216
269
  return []
217
270
  end
218
-
271
+
219
272
  def setup_dns_record(token, zone, domain_info, existing_records)
220
273
  puts "\n⚙️ DNS 레코드를 설정합니다...".colorize(:yellow)
221
274
 
222
- # 홈서버 IP/URL 입력받기
223
275
  prompt = TTY::Prompt.new
224
276
 
225
- server_info = prompt.ask("홈서버 IP 또는 도메인을 입력하세요:") do |q|
277
+ server_info = prompt.ask("연결할 서버 IP 또는 도메인을 입력하세요:") do |q|
226
278
  q.validate(/\A.+\z/, "서버 정보를 입력해주세요")
227
279
  end
228
280
 
229
- # SSH 사용자 계정 입력받기
230
281
  ssh_user = prompt.ask("SSH 사용자 계정을 입력하세요:", default: "root")
231
282
 
232
- # IP인지 도메인인지 판단
233
283
  is_ip = server_info.match?(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/)
234
284
  record_type = is_ip ? 'A' : 'CNAME'
235
285
 
236
286
  zone_id = zone['id']
237
287
  zone_name = zone['name']
238
288
 
239
- # 도메인 정보에 따라 레코드 설정
240
289
  final_domain = determine_final_domain(domain_info, zone_name, existing_records)
241
-
242
- # 대상 도메인의 모든 A/CNAME 레코드 확인
243
290
  all_records = get_dns_records(token, zone_id, final_domain[:name], ['A', 'CNAME'])
244
291
 
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)
252
- else
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'])
292
+ is_already_configured = all_records.length == 1 &&
293
+ all_records.first['type'] == record_type &&
294
+ all_records.first['content'] == server_info
295
+
296
+ if is_already_configured
297
+ puts "✅ DNS 레코드가 이미 올바르게 설정되어 있습니다.".colorize(:green)
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 "--------------------------------------------------"
311
+
312
+ message = "이 레코드를 삭제하고 새로 설정하시겠습니까? (이 작업은 되돌릴 수 없습니다)"
313
+ unless prompt.yes?(message)
314
+ puts "❌ DNS 설정이 사용자에 의해 취소되었습니다. 스크립트를 종료합니다.".colorize(:red)
315
+ exit 0
316
+ end
259
317
 
260
- #레코드 생성
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
+
324
+ else
325
+ # 기존 레코드가 없으면 바로 생성합니다.
261
326
  create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
262
327
  end
263
- else
264
- # DNS 레코드 생성
265
- create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
266
328
  end
267
329
 
268
- # 최종 도메인 정보 저장
269
330
  @final_domain = final_domain[:full_domain]
270
331
  @server_info = server_info
271
332
  @ssh_user = ssh_user
272
333
  end
273
-
334
+
274
335
  def determine_final_domain(domain_info, zone_name, existing_records)
275
336
  case domain_info[:type]
276
337
  when :root
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
338
+ { name: zone_name, full_domain: zone_name }
283
339
  when :subdomain
284
340
  { name: domain_info[:domain], full_domain: domain_info[:domain] }
285
341
  end
@@ -298,7 +354,8 @@ module Tayo
298
354
  type: type,
299
355
  name: name,
300
356
  content: content,
301
- ttl: 300
357
+ ttl: 300,
358
+ proxied: true
302
359
  }
303
360
 
304
361
  request.body = data.to_json
@@ -306,7 +363,7 @@ module Tayo
306
363
 
307
364
  if response.code == '200'
308
365
  puts "✅ DNS 레코드가 생성되었습니다.".colorize(:green)
309
- puts " #{name} → #{content} (#{type} 레코드)".colorize(:gray)
366
+ puts " #{name} → #{content} (#{type} 레코드, 프록시됨)".colorize(:gray)
310
367
  else
311
368
  puts "❌ DNS 레코드 생성에 실패했습니다: #{response.code}".colorize(:red)
312
369
  puts response.body
@@ -328,16 +385,12 @@ module Tayo
328
385
 
329
386
  response = http.request(request)
330
387
 
331
- if response.code == '200'
332
- puts "✅ 기존 DNS 레코드가 삭제되었습니다.".colorize(:green)
333
- else
388
+ unless response.code == '200'
334
389
  puts "❌ DNS 레코드 삭제에 실패했습니다: #{response.code}".colorize(:red)
335
390
  puts response.body
336
- exit 1
337
391
  end
338
392
  rescue => e
339
393
  puts "❌ DNS 레코드 삭제 중 오류: #{e.message}".colorize(:red)
340
- exit 1
341
394
  end
342
395
 
343
396
  def update_deploy_config(domain_info)
@@ -356,27 +409,23 @@ module Tayo
356
409
  if content.include?("proxy:")
357
410
  content.gsub!(/(\s+host:\s+).*$/, "\\1#{@final_domain}")
358
411
  else
359
- # proxy 섹션이 없으면 추가
360
412
  proxy_config = "\n# Proxy configuration\nproxy:\n ssl: true\n host: #{@final_domain}\n"
361
413
  content += proxy_config
362
414
  end
363
415
 
364
416
  # servers 설정 업데이트
365
417
  if content.match?(/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}")
418
+ content.gsub!(/(\s*servers:\s*\n\s*web:\s*\n\s*-\s*)[\w.-]+/, "\\1#{@server_info}")
367
419
  end
368
420
 
369
421
  # ssh user 설정 업데이트
370
422
  if @ssh_user && @ssh_user != "root"
371
423
  if content.match?(/^ssh:/)
372
- # 기존 ssh 섹션 업데이트
373
424
  content.gsub!(/^ssh:\s*\n\s*user:\s*\w+/, "ssh:\n user: #{@ssh_user}")
374
425
  else
375
- # ssh 섹션 추가 (accessories 섹션 앞에 추가)
376
426
  if content.match?(/^# Use accessory services/)
377
427
  content.gsub!(/^# Use accessory services/, "# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n\n# Use accessory services")
378
428
  else
379
- # 파일 끝에 추가
380
429
  content += "\n# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n"
381
430
  end
382
431
  end
@@ -386,31 +435,28 @@ module Tayo
386
435
  puts "✅ config/deploy.yml이 업데이트되었습니다.".colorize(:green)
387
436
  puts " proxy.host: #{@final_domain}".colorize(:gray)
388
437
  puts " servers.web: #{@server_info}".colorize(:gray)
389
- puts " ssh.user: #{@ssh_user}".colorize(:gray) if @ssh_user && @ssh_user != "root"
438
+ if @ssh_user && @ssh_user != "root"
439
+ puts " ssh.user: #{@ssh_user}".colorize(:gray)
440
+ end
390
441
  end
391
442
 
392
443
  def commit_cloudflare_changes(domain_info)
393
444
  puts "\n📝 변경사항을 Git에 커밋합니다...".colorize(:yellow)
394
445
 
395
- # 변경된 파일이 있는지 확인
396
- status_output = `git status --porcelain`.strip
446
+ status_output = `git status --porcelain config/deploy.yml`.strip
397
447
 
398
448
  if status_output.empty?
399
- puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:yellow)
449
+ puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:cyan)
400
450
  return
401
451
  end
402
452
 
403
- # Git add
404
- system("git add -A")
453
+ system("git add config/deploy.yml")
405
454
 
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"
455
+ commit_message = "feat: Configure Cloudflare DNS for #{@final_domain}\n\n- Set DNS record for #{@final_domain} to point to #{@server_info}\n- Update deployment configuration in config/deploy.yml\n\n🤖 Generated by Tayo"
408
456
 
409
- # Commit 실행
410
457
  if system("git commit -m \"#{commit_message}\"")
411
458
  puts "✅ 변경사항이 성공적으로 커밋되었습니다.".colorize(:green)
412
459
 
413
- # GitHub에 푸시
414
460
  if system("git push", out: File::NULL, err: File::NULL)
415
461
  puts "✅ 변경사항이 GitHub에 푸시되었습니다.".colorize(:green)
416
462
  else
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "colorize"
4
- require_relative "../dockerfile_modifier"
4
+ # require_relative "../dockerfile_modifier"
5
5
 
6
6
  module Tayo
7
7
  module Commands
@@ -17,8 +17,7 @@ module Tayo
17
17
  check_orbstack
18
18
  create_welcome_page
19
19
  clear_docker_cache
20
- ensure_dockerfile_exists
21
- disable_bootsnap_in_dockerfile
20
+ ensure_dockerfile_exists
22
21
  commit_changes
23
22
  puts "✅ Tayo가 성공적으로 설정되었습니다!".colorize(:green)
24
23
  end
@@ -83,16 +82,6 @@ module Tayo
83
82
  puts "✅ Dockerfile이 이미 존재합니다.".colorize(:green)
84
83
  end
85
84
  end
86
-
87
- def disable_bootsnap_in_dockerfile
88
- puts "🔧 Dockerfile에서 bootsnap을 비활성화합니다...".colorize(:yellow)
89
- begin
90
- modifier = DockerfileModifier.new
91
- modifier.init
92
- rescue => e
93
- puts "⚠️ Dockerfile 수정 중 오류가 발생했습니다: #{e.message}".colorize(:yellow)
94
- end
95
- end
96
85
 
97
86
  def create_welcome_page
98
87
  # Welcome 컨트롤러가 이미 있는지 확인
data/lib/tayo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tayo
4
- VERSION = "0.1.11"
4
+ VERSION = "0.1.12"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tayo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - 이원섭wonsup Lee/Alfonso
@@ -75,6 +75,8 @@ extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - ".DS_Store"
78
+ - CHANGELOG.md
79
+ - CLAUDE.md
78
80
  - README.md
79
81
  - Rakefile
80
82
  - exe/tayo