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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "colorize"
|
|
4
|
+
require "tty-prompt"
|
|
5
|
+
|
|
6
|
+
module Tayo
|
|
7
|
+
module Proxy
|
|
8
|
+
class NetworkConfig
|
|
9
|
+
attr_reader :public_ip, :internal_ip, :external_http, :external_https
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@prompt = TTY::Prompt.new
|
|
13
|
+
@use_custom_ports = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def detect_ips
|
|
17
|
+
puts "\n๐ ๋คํธ์ํฌ ์ ๋ณด๋ฅผ ํ์ธํฉ๋๋ค...".colorize(:yellow)
|
|
18
|
+
|
|
19
|
+
# ๊ณต์ธ IP ๊ฐ์ง
|
|
20
|
+
print "๊ณต์ธ IP ํ์ธ ์ค... "
|
|
21
|
+
@public_ip = detect_public_ip
|
|
22
|
+
puts "#{@public_ip}".colorize(:green)
|
|
23
|
+
|
|
24
|
+
# ๋ด๋ถ IP ๊ฐ์ง
|
|
25
|
+
print "๋ด๋ถ IP ํ์ธ ์ค... "
|
|
26
|
+
@internal_ip = detect_internal_ip
|
|
27
|
+
puts "#{@internal_ip}".colorize(:green)
|
|
28
|
+
|
|
29
|
+
puts ""
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def configure_ports
|
|
33
|
+
puts "\n๐ ์ธ๋ถ ์ ์ ํฌํธ๋ฅผ ์ค์ ํฉ๋๋ค.".colorize(:yellow)
|
|
34
|
+
puts "Kamal Proxy๋ ํญ์ 80, 443 ํฌํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค.".colorize(:gray)
|
|
35
|
+
puts ""
|
|
36
|
+
|
|
37
|
+
choices = [
|
|
38
|
+
{ name: "๊ณต์ ๊ธฐ์์ 80, 443์ ์ง์ ํฌ์๋ฉ (๊ธฐ๋ณธ)", value: :direct },
|
|
39
|
+
{ name: "๋ค๋ฅธ ํฌํธ๋ฅผ ์ฌ์ฉํ์ฌ ํฌ์๋ฉ (์: 8080โ80, 8443โ443)", value: :custom }
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
choice = @prompt.select("ํฌํธ ์ค์ ๋ฐฉ์์ ์ ํํ์ธ์:", choices)
|
|
43
|
+
|
|
44
|
+
if choice == :custom
|
|
45
|
+
@use_custom_ports = true
|
|
46
|
+
|
|
47
|
+
@external_http = @prompt.ask("HTTP ์ธ๋ถ ํฌํธ (๊ธฐ๋ณธ: 8080):", default: "8080")
|
|
48
|
+
@external_https = @prompt.ask("HTTPS ์ธ๋ถ ํฌํธ (๊ธฐ๋ณธ: 8443):", default: "8443")
|
|
49
|
+
|
|
50
|
+
puts "\nโ
์ธ๋ถ ํฌํธ๊ฐ ์ค์ ๋์์ต๋๋ค:".colorize(:green)
|
|
51
|
+
puts " HTTP: #{@external_http}".colorize(:gray)
|
|
52
|
+
puts " HTTPS: #{@external_https}".colorize(:gray)
|
|
53
|
+
else
|
|
54
|
+
@external_http = "80"
|
|
55
|
+
@external_https = "443"
|
|
56
|
+
|
|
57
|
+
puts "\nโ
ํ์ค ํฌํธ(80, 443)๋ฅผ ์ฌ์ฉํฉ๋๋ค.".colorize(:green)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def use_custom_ports?
|
|
62
|
+
@use_custom_ports
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def show_port_forwarding_guide
|
|
66
|
+
return unless use_custom_ports?
|
|
67
|
+
|
|
68
|
+
puts "\n๐ก ๊ณต์ ๊ธฐ ํฌํธํฌ์๋ฉ ์ค์ ์๋ด:".colorize(:yellow)
|
|
69
|
+
puts "โ" * 50
|
|
70
|
+
puts "์ธ๋ถ ํฌํธ #{@external_http} โ #{@internal_ip}:80".colorize(:white)
|
|
71
|
+
puts "์ธ๋ถ ํฌํธ #{@external_https} โ #{@internal_ip}:443".colorize(:white)
|
|
72
|
+
puts "โ" * 50
|
|
73
|
+
puts ""
|
|
74
|
+
puts "์ ์ค์ ์ ๊ณต์ ๊ธฐ ๊ด๋ฆฌ ํ์ด์ง์์ ์๋ฃํด์ฃผ์ธ์.".colorize(:cyan)
|
|
75
|
+
puts "์ผ๋ฐ์ ์ธ ์ ์ ์ฃผ์: http://192.168.1.1".colorize(:gray)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def detect_public_ip
|
|
81
|
+
# curl์ ์ฌ์ฉํ ๊ณต์ธ IP ๊ฐ์ง
|
|
82
|
+
ip = `curl -s ifconfig.me 2>/dev/null`.strip
|
|
83
|
+
|
|
84
|
+
# ๋์ฒด ๋ฐฉ๋ฒ๋ค
|
|
85
|
+
if ip.empty? || !valid_ip?(ip)
|
|
86
|
+
ip = `curl -s ipecho.net/plain 2>/dev/null`.strip
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if ip.empty? || !valid_ip?(ip)
|
|
90
|
+
ip = `curl -s icanhazip.com 2>/dev/null`.strip
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if ip.empty? || !valid_ip?(ip)
|
|
94
|
+
puts "\nโ ๏ธ ๊ณต์ธ IP๋ฅผ ์๋์ผ๋ก ๊ฐ์งํ ์ ์์ต๋๋ค.".colorize(:yellow)
|
|
95
|
+
ip = @prompt.ask("๊ณต์ธ IP๋ฅผ ์ง์ ์
๋ ฅํด์ฃผ์ธ์:") do |q|
|
|
96
|
+
q.validate(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/, "์ฌ๋ฐ๋ฅธ IP ํ์์ ์
๋ ฅํด์ฃผ์ธ์")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
ip
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def detect_internal_ip
|
|
104
|
+
# macOS
|
|
105
|
+
if RUBY_PLATFORM.include?("darwin")
|
|
106
|
+
# en0 (Wi-Fi) ๋๋ en1 (Ethernet) ์ธํฐํ์ด์ค์์ IP ์ถ์ถ
|
|
107
|
+
ip = `ifconfig en0 2>/dev/null | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}'`.strip
|
|
108
|
+
ip = `ifconfig en1 2>/dev/null | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}'`.strip if ip.empty?
|
|
109
|
+
|
|
110
|
+
# ๋ค๋ฅธ ์ธํฐํ์ด์ค ๊ฒ์
|
|
111
|
+
if ip.empty?
|
|
112
|
+
ip = `ifconfig | grep 'inet ' | grep -v 127.0.0.1 | grep -v '::1' | head -1 | awk '{print $2}'`.strip
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
# Linux
|
|
116
|
+
ip = `hostname -I 2>/dev/null | awk '{print $1}'`.strip
|
|
117
|
+
|
|
118
|
+
if ip.empty?
|
|
119
|
+
ip = `ip addr show | grep 'inet ' | grep -v 127.0.0.1 | grep -v '::1' | head -1 | awk '{print $2}' | cut -d/ -f1`.strip
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# ์ฌ์ ํ ๋น์ด์๋ค๋ฉด ์๋ ์
๋ ฅ
|
|
124
|
+
if ip.empty? || !valid_ip?(ip)
|
|
125
|
+
puts "\nโ ๏ธ ๋ด๋ถ IP๋ฅผ ์๋์ผ๋ก ๊ฐ์งํ ์ ์์ต๋๋ค.".colorize(:yellow)
|
|
126
|
+
ip = @prompt.ask("๋ด๋ถ IP๋ฅผ ์ง์ ์
๋ ฅํด์ฃผ์ธ์ (์: 192.168.1.100):") do |q|
|
|
127
|
+
q.validate(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/, "์ฌ๋ฐ๋ฅธ IP ํ์์ ์
๋ ฅํด์ฃผ์ธ์")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
ip
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def valid_ip?(ip)
|
|
135
|
+
return false if ip.nil? || ip.empty?
|
|
136
|
+
|
|
137
|
+
parts = ip.split('.')
|
|
138
|
+
return false unless parts.length == 4
|
|
139
|
+
|
|
140
|
+
parts.all? do |part|
|
|
141
|
+
num = part.to_i
|
|
142
|
+
num >= 0 && num <= 255 && part == num.to_s
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "colorize"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "yaml"
|
|
6
|
+
require "erb"
|
|
7
|
+
|
|
8
|
+
module Tayo
|
|
9
|
+
module Proxy
|
|
10
|
+
class TraefikConfig
|
|
11
|
+
TRAEFIK_CONFIG_DIR = File.expand_path("~/.tayo/traefik")
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@docker = DockerManager.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def setup(domains, email = nil)
|
|
18
|
+
puts "\nโ๏ธ Traefik์ ์ค์ ํฉ๋๋ค...".colorize(:yellow)
|
|
19
|
+
|
|
20
|
+
# ์ค์ ๋๋ ํ ๋ฆฌ ์์ฑ
|
|
21
|
+
setup_directories
|
|
22
|
+
|
|
23
|
+
# ์ด๋ฉ์ผ ์ฃผ์ ์
๋ ฅ (Let's Encrypt์ฉ)
|
|
24
|
+
email ||= get_email_for_acme
|
|
25
|
+
|
|
26
|
+
# ์ค์ ํ์ผ ์์ฑ
|
|
27
|
+
create_docker_compose(email)
|
|
28
|
+
create_traefik_config(email)
|
|
29
|
+
create_dynamic_config(domains)
|
|
30
|
+
|
|
31
|
+
# Traefik ์์
|
|
32
|
+
ensure_running
|
|
33
|
+
|
|
34
|
+
# ๋ผ์ฐํ
์ค์
|
|
35
|
+
configure_routes(domains)
|
|
36
|
+
|
|
37
|
+
puts "โ
Traefik ์ค์ ์ด ์๋ฃ๋์์ต๋๋ค.".colorize(:green)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def setup_directories
|
|
43
|
+
FileUtils.mkdir_p(TRAEFIK_CONFIG_DIR)
|
|
44
|
+
FileUtils.mkdir_p(File.join(TRAEFIK_CONFIG_DIR, "config"))
|
|
45
|
+
|
|
46
|
+
# acme.json ํ์ผ ์์ฑ ๋ฐ ๊ถํ ์ค์
|
|
47
|
+
acme_file = File.join(TRAEFIK_CONFIG_DIR, "acme.json")
|
|
48
|
+
unless File.exist?(acme_file)
|
|
49
|
+
File.write(acme_file, "{}")
|
|
50
|
+
File.chmod(0600, acme_file)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def get_email_for_acme
|
|
55
|
+
prompt = TTY::Prompt.new
|
|
56
|
+
|
|
57
|
+
# ์ ์ฅ๋ ์ด๋ฉ์ผ ํ์ธ
|
|
58
|
+
email_file = File.join(TRAEFIK_CONFIG_DIR, ".email")
|
|
59
|
+
if File.exist?(email_file)
|
|
60
|
+
saved_email = File.read(email_file).strip
|
|
61
|
+
if prompt.yes?("์ ์ฅ๋ ์ด๋ฉ์ผ์ ์ฌ์ฉํ์๊ฒ ์ต๋๊น? (#{saved_email})")
|
|
62
|
+
return saved_email
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ์ ์ด๋ฉ์ผ ์
๋ ฅ
|
|
67
|
+
email = prompt.ask("Let's Encrypt ์ธ์ฆ์์ฉ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์
๋ ฅํ์ธ์:") do |q|
|
|
68
|
+
q.validate(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i, "์ฌ๋ฐ๋ฅธ ์ด๋ฉ์ผ ํ์์ ์
๋ ฅํด์ฃผ์ธ์")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# ์ด๋ฉ์ผ ์ ์ฅ
|
|
72
|
+
File.write(email_file, email)
|
|
73
|
+
email
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def create_docker_compose(email)
|
|
77
|
+
compose_content = <<~YAML
|
|
78
|
+
version: '3.8'
|
|
79
|
+
|
|
80
|
+
services:
|
|
81
|
+
traefik:
|
|
82
|
+
image: traefik:v3.0
|
|
83
|
+
container_name: traefik
|
|
84
|
+
restart: unless-stopped
|
|
85
|
+
security_opt:
|
|
86
|
+
- no-new-privileges:true
|
|
87
|
+
networks:
|
|
88
|
+
- traefik-net
|
|
89
|
+
ports:
|
|
90
|
+
- "80:80"
|
|
91
|
+
- "443:443"
|
|
92
|
+
- "8080:8080" # ๋์๋ณด๋
|
|
93
|
+
extra_hosts:
|
|
94
|
+
- "host.docker.internal:host-gateway"
|
|
95
|
+
volumes:
|
|
96
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
97
|
+
- #{TRAEFIK_CONFIG_DIR}/config/traefik.yml:/etc/traefik/traefik.yml:ro
|
|
98
|
+
- #{TRAEFIK_CONFIG_DIR}/config/dynamic.yml:/etc/traefik/dynamic.yml:ro
|
|
99
|
+
- #{TRAEFIK_CONFIG_DIR}/acme.json:/acme.json
|
|
100
|
+
labels:
|
|
101
|
+
- "traefik.enable=true"
|
|
102
|
+
- "traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)"
|
|
103
|
+
- "traefik.http.routers.dashboard.service=api@internal"
|
|
104
|
+
- "traefik.http.routers.dashboard.middlewares=auth"
|
|
105
|
+
- "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$10$$YFPx3EmK6lN5bPG.zPNvp.UYQhkPvNnkZ7J4zYu2GODXJfHZXfYbK" # admin:admin
|
|
106
|
+
|
|
107
|
+
networks:
|
|
108
|
+
traefik-net:
|
|
109
|
+
name: traefik-net
|
|
110
|
+
driver: bridge
|
|
111
|
+
YAML
|
|
112
|
+
|
|
113
|
+
compose_file = File.join(TRAEFIK_CONFIG_DIR, "docker-compose.yml")
|
|
114
|
+
File.write(compose_file, compose_content)
|
|
115
|
+
puts "โ
Docker Compose ํ์ผ์ด ์์ฑ๋์์ต๋๋ค.".colorize(:green)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def create_traefik_config(email = nil)
|
|
119
|
+
# ์ด๋ฉ์ผ ์ฃผ์ ํ์ธ
|
|
120
|
+
email_file = File.join(TRAEFIK_CONFIG_DIR, ".email")
|
|
121
|
+
email ||= File.exist?(email_file) ? File.read(email_file).strip : "admin@example.com"
|
|
122
|
+
|
|
123
|
+
config_content = <<~YAML
|
|
124
|
+
# Traefik ์ ์ ์ค์
|
|
125
|
+
api:
|
|
126
|
+
dashboard: true
|
|
127
|
+
debug: false
|
|
128
|
+
|
|
129
|
+
entryPoints:
|
|
130
|
+
web:
|
|
131
|
+
address: ":80"
|
|
132
|
+
http:
|
|
133
|
+
redirections:
|
|
134
|
+
entryPoint:
|
|
135
|
+
to: websecure
|
|
136
|
+
scheme: https
|
|
137
|
+
permanent: true
|
|
138
|
+
websecure:
|
|
139
|
+
address: ":443"
|
|
140
|
+
|
|
141
|
+
providers:
|
|
142
|
+
docker:
|
|
143
|
+
endpoint: "unix:///var/run/docker.sock"
|
|
144
|
+
exposedByDefault: false
|
|
145
|
+
network: traefik-net
|
|
146
|
+
watch: true
|
|
147
|
+
file:
|
|
148
|
+
filename: /etc/traefik/dynamic.yml
|
|
149
|
+
watch: true
|
|
150
|
+
|
|
151
|
+
certificatesResolvers:
|
|
152
|
+
myresolver:
|
|
153
|
+
acme:
|
|
154
|
+
email: #{email}
|
|
155
|
+
storage: /acme.json
|
|
156
|
+
tlsChallenge: {}
|
|
157
|
+
# caServer: https://acme-staging-v02.api.letsencrypt.org/directory # ํ
์คํธ์ฉ
|
|
158
|
+
|
|
159
|
+
log:
|
|
160
|
+
level: INFO
|
|
161
|
+
format: json
|
|
162
|
+
|
|
163
|
+
accessLog:
|
|
164
|
+
format: json
|
|
165
|
+
YAML
|
|
166
|
+
|
|
167
|
+
config_file = File.join(TRAEFIK_CONFIG_DIR, "config", "traefik.yml")
|
|
168
|
+
File.write(config_file, config_content)
|
|
169
|
+
puts "โ
Traefik ์ค์ ํ์ผ์ด ์์ฑ๋์์ต๋๋ค.".colorize(:green)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def create_dynamic_config(domains)
|
|
173
|
+
routers = {}
|
|
174
|
+
services = {}
|
|
175
|
+
|
|
176
|
+
domains.each do |domain_info|
|
|
177
|
+
domain = domain_info[:domain]
|
|
178
|
+
safe_name = domain.gsub('.', '-').gsub('_', '-')
|
|
179
|
+
|
|
180
|
+
# HTTP ๋ผ์ฐํฐ (๋ฆฌ๋ค์ด๋ ํธ์ฉ)
|
|
181
|
+
routers["#{safe_name}-http"] = {
|
|
182
|
+
"rule" => "Host(`#{domain}`)",
|
|
183
|
+
"entryPoints" => ["web"],
|
|
184
|
+
"middlewares" => ["redirect-to-https"],
|
|
185
|
+
"service" => "#{safe_name}-service"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# HTTPS ๋ผ์ฐํฐ
|
|
189
|
+
routers["#{safe_name}-https"] = {
|
|
190
|
+
"rule" => "Host(`#{domain}`)",
|
|
191
|
+
"entryPoints" => ["websecure"],
|
|
192
|
+
"service" => "#{safe_name}-service",
|
|
193
|
+
"tls" => {
|
|
194
|
+
"certResolver" => "myresolver"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# ์๋น์ค ์ ์ (ํธ์คํธ์ 3000 ํฌํธ๋ก)
|
|
199
|
+
services["#{safe_name}-service"] = {
|
|
200
|
+
"loadBalancer" => {
|
|
201
|
+
"servers" => [
|
|
202
|
+
{ "url" => "http://host.docker.internal:3000" }
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# ๋ฏธ๋ค์จ์ด ์ ์
|
|
209
|
+
dynamic_config = {
|
|
210
|
+
"http" => {
|
|
211
|
+
"middlewares" => {
|
|
212
|
+
"redirect-to-https" => {
|
|
213
|
+
"redirectScheme" => {
|
|
214
|
+
"scheme" => "https",
|
|
215
|
+
"permanent" => true
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"routers" => routers,
|
|
220
|
+
"services" => services
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
dynamic_file = File.join(TRAEFIK_CONFIG_DIR, "config", "dynamic.yml")
|
|
225
|
+
File.write(dynamic_file, dynamic_config.to_yaml)
|
|
226
|
+
puts "โ
๋์ ๋ผ์ฐํ
์ค์ ์ด ์์ฑ๋์์ต๋๋ค.".colorize(:green)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def ensure_running
|
|
230
|
+
if @docker.container_running?("traefik")
|
|
231
|
+
puts "๐ Traefik ์ปจํ
์ด๋๋ฅผ ์ฌ์์ํฉ๋๋ค...".colorize(:yellow)
|
|
232
|
+
reload_traefik
|
|
233
|
+
else
|
|
234
|
+
start_container
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def start_container
|
|
239
|
+
puts "๐ Traefik ์ปจํ
์ด๋๋ฅผ ์์ํฉ๋๋ค...".colorize(:yellow)
|
|
240
|
+
|
|
241
|
+
# ๊ธฐ์กด ์ปจํ
์ด๋๊ฐ ์๋ค๋ฉด ์ ๊ฑฐ
|
|
242
|
+
if @docker.container_exists?("traefik")
|
|
243
|
+
@docker.stop_container("traefik")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Docker Compose๋ก ์์
|
|
247
|
+
Dir.chdir(TRAEFIK_CONFIG_DIR) do
|
|
248
|
+
if system("docker compose up -d")
|
|
249
|
+
puts "โ
Traefik์ด ์์๋์์ต๋๋ค.".colorize(:green)
|
|
250
|
+
|
|
251
|
+
# ์์ ์๋ฃ ๋๊ธฐ
|
|
252
|
+
sleep 3
|
|
253
|
+
|
|
254
|
+
# ์ํ ํ์ธ
|
|
255
|
+
check_traefik_status
|
|
256
|
+
else
|
|
257
|
+
puts "โ Traefik ์์์ ์คํจํ์ต๋๋ค.".colorize(:red)
|
|
258
|
+
exit 1
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def reload_traefik
|
|
264
|
+
puts "๐ Traefik ์ค์ ์ ๋ค์ ๋ก๋ํฉ๋๋ค...".colorize(:yellow)
|
|
265
|
+
|
|
266
|
+
Dir.chdir(TRAEFIK_CONFIG_DIR) do
|
|
267
|
+
if system("docker compose restart")
|
|
268
|
+
puts "โ
Traefik์ด ์ฌ์์๋์์ต๋๋ค.".colorize(:green)
|
|
269
|
+
else
|
|
270
|
+
puts "โ ๏ธ Traefik ์ฌ์์์ ์คํจํ์ต๋๋ค.".colorize(:yellow)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def check_traefik_status
|
|
276
|
+
# ๋์๋ณด๋ ์ ๊ทผ ํ์ธ
|
|
277
|
+
puts "\n๐ Traefik ๋์๋ณด๋: http://localhost:8080".colorize(:cyan)
|
|
278
|
+
puts " (๊ธฐ๋ณธ ์ธ์ฆ: admin / admin)".colorize(:gray)
|
|
279
|
+
|
|
280
|
+
# ๋ก๊ทธ ํ์ธ
|
|
281
|
+
logs = `docker logs traefik --tail 5 2>&1`.strip
|
|
282
|
+
if logs.include?("error") || logs.include?("Error")
|
|
283
|
+
puts "\nโ ๏ธ Traefik ๋ก๊ทธ์ ์ค๋ฅ๊ฐ ๋ฐ๊ฒฌ๋์์ต๋๋ค:".colorize(:yellow)
|
|
284
|
+
puts logs.colorize(:gray)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def configure_routes(domains)
|
|
289
|
+
puts "\n๐ ๋๋ฉ์ธ ๋ผ์ฐํ
์ํ:".colorize(:yellow)
|
|
290
|
+
|
|
291
|
+
domains.each do |domain_info|
|
|
292
|
+
domain = domain_info[:domain]
|
|
293
|
+
puts " โข #{domain} โ localhost:3000".colorize(:green)
|
|
294
|
+
puts " HTTP: http://#{domain} (โ HTTPS ๋ฆฌ๋ค์ด๋ ํธ)".colorize(:gray)
|
|
295
|
+
puts " HTTPS: https://#{domain} (Let's Encrypt ์ธ์ฆ์)".colorize(:gray)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
puts "\n๐ก Let's Encrypt ์ธ์ฆ์ ๋ฐ๊ธ ์ค...".colorize(:yellow)
|
|
299
|
+
puts " ์ฒซ ๋ฐ๊ธ์๋ 1-2๋ถ์ด ์์๋ ์ ์์ต๋๋ค.".colorize(:gray)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|