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 +4 -4
- data/CHANGELOG.md +57 -0
- data/CLAUDE.md +58 -0
- data/README.md +51 -21
- data/lib/tayo/commands/cf.rb +162 -116
- data/lib/tayo/commands/init.rb +2 -13
- data/lib/tayo/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a5fae5e5027e6af704d0de853a7402c0011dbe9e4e9638bd8e05553a60d9f13
|
4
|
+
data.tar.gz: 7b0446e1db678ae717d49176d415095820622e19b3f75812182090a9ff32aa89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
- 토큰을
|
80
|
-
-
|
81
|
-
|
82
|
-
- Zone
|
78
|
+
- 토큰을 `~/.tayo` 파일에 안전하게 저장하여 재사용
|
79
|
+
- 필요한 권한: Zone:Read, DNS:Edit
|
80
|
+
- **도메인 설정**:
|
81
|
+
- Cloudflare 계정의 Zone 목록에서 도메인 선택
|
82
|
+
- 루트 도메인(@) 또는 서브도메인 설정 지원
|
83
|
+
- 대화형 UI로 쉽게 설정 가능
|
83
84
|
- **DNS 레코드 생성/업데이트**:
|
84
|
-
- A 레코드 생성:
|
85
|
-
-
|
85
|
+
- A 레코드 생성: IP 주소로 연결
|
86
|
+
- CNAME 레코드 생성: 도메인으로 연결
|
87
|
+
- 기존 레코드가 있으면 사용자 확인 후 업데이트
|
86
88
|
- Proxied 설정 (Cloudflare CDN 사용)
|
87
|
-
-
|
88
|
-
-
|
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
|
-
|
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
|
data/lib/tayo/commands/cf.rb
CHANGED
@@ -17,30 +17,27 @@ module Tayo
|
|
17
17
|
return
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
21
|
-
domain_info = get_domain_input
|
20
|
+
# --- 로직 순서 변경 ---
|
22
21
|
|
23
|
-
# 2.
|
24
|
-
open_token_creation_page
|
25
|
-
|
26
|
-
# 3. 토큰 입력받기
|
22
|
+
# 2. 토큰 입력받기
|
27
23
|
token = get_cloudflare_token
|
28
24
|
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
#
|
32
|
+
# 5. DNS 레코드 추가/수정 (루트 도메인 덮어쓰기 로직 포함)
|
36
33
|
setup_dns_record(token, selected_zone, domain_info, existing_records)
|
37
34
|
|
38
|
-
#
|
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
|
-
|
54
|
-
|
50
|
+
# [신규] Cloudflare Zone 목록에서 도메인을 선택하고 구성하는 메소드
|
51
|
+
def configure_domain_from_zones(token)
|
52
|
+
puts "\n🌐 Cloudflare 계정의 도메인 목록을 조회합니다...".colorize(:yellow)
|
55
53
|
|
56
|
-
|
54
|
+
zones = get_cloudflare_zones(token)
|
57
55
|
|
58
|
-
|
59
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
178
|
-
|
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 "
|
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("
|
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
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
puts "
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
-
|
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}
|
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
|
-
|
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*)[\
|
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
|
-
|
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(:
|
449
|
+
puts "ℹ️ 커밋할 변경사항이 없습니다.".colorize(:cyan)
|
400
450
|
return
|
401
451
|
end
|
402
452
|
|
403
|
-
|
404
|
-
system("git add -A")
|
453
|
+
system("git add config/deploy.yml")
|
405
454
|
|
406
|
-
#
|
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
|
data/lib/tayo/commands/init.rb
CHANGED
@@ -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
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.
|
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
|