diff --git a/Makefile b/Makefile index f9afeea..7d0bfd6 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,10 @@ CLIENT_BIN := $(BIN_DIR)/hop-gate-client VERSION ?= $(shell git describe --tags --dirty --always 2>/dev/null || echo dev) +# .env 파일 로드 +include .env +export $(shell sed 's/=.*//' .env) + .PHONY: all server client clean docker-server run-server run-client errors-css all: server client @@ -38,13 +42,13 @@ errors-css: echo "package.json not found; skipping errors-css build"; \ fi -server: errors-css +server: errors-css check-env-server @echo "Building server..." @mkdir -p $(BIN_DIR) $(GO) build -ldflags "-X main.version=$(VERSION)" -o $(SERVER_BIN) $(SERVER_PKG) @echo "Server binary: $(SERVER_BIN)" -client: +client: check-env-client @echo "Building client..." @mkdir -p $(BIN_DIR) $(GO) build -ldflags "-X main.version=$(VERSION)" -o $(CLIENT_BIN) $(CLIENT_PKG) @@ -66,3 +70,15 @@ docker-server: @echo "Building server Docker image..." docker build -f Dockerfile.server -t hop-gate-server:$(VERSION) . +check-env-server: + @if [ -z "$$HOP_SERVER_HTTP_LISTEN" ]; then echo "필수 환경 변수 HOP_SERVER_HTTP_LISTEN가 설정되지 않았습니다."; exit 1; fi + @if [ -z "$$HOP_SERVER_HTTPS_LISTEN" ]; then echo "필수 환경 변수 HOP_SERVER_HTTPS_LISTEN가 설정되지 않았습니다."; exit 1; fi + @if [ -z "$$HOP_SERVER_DTLS_LISTEN" ]; then echo "필수 환경 변수 HOP_SERVER_DTLS_LISTEN가 설정되지 않았습니다."; exit 1; fi + @if [ -z "$$HOP_SERVER_DOMAIN" ]; then echo "필수 환경 변수 HOP_SERVER_DOMAIN가 설정되지 않았습니다."; exit 1; fi + +check-env-client: + @if [ -z "$$HOP_CLIENT_SERVER_ADDR" ]; then echo "필수 환경 변수 HOP_CLIENT_SERVER_ADDR가 설정되지 않았습니다."; exit 1; fi + @if [ -z "$$HOP_CLIENT_DOMAIN" ]; then echo "필수 환경 변수 HOP_CLIENT_DOMAIN가 설정되지 않았습니다."; exit 1; fi + @if [ -z "$$HOP_CLIENT_API_KEY" ]; then echo "필수 환경 변수 HOP_CLIENT_API_KEY가 설정되지 않았습니다."; exit 1; fi + @if [ -z "$$HOP_CLIENT_LOCAL_TARGET" ]; then echo "필수 환경 변수 HOP_CLIENT_LOCAL_TARGET가 설정되지 않았습니다."; exit 1; fi + @if [ -z "$$HOP_CLIENT_DEBUG" ]; then echo "필수 환경 변수 HOP_CLIENT_DEBUG가 설정되지 않았습니다."; exit 1; fi \ No newline at end of file diff --git a/cmd/client/main.go b/cmd/client/main.go index 86e2b2a..0f9003d 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "crypto/x509" "flag" + "log" "net" "os" "strings" @@ -13,8 +14,25 @@ import ( "github.com/dalbodeule/hop-gate/internal/dtls" "github.com/dalbodeule/hop-gate/internal/logging" "github.com/dalbodeule/hop-gate/internal/proxy" + + "github.com/joho/godotenv" ) +func init() { + // .env 파일 로드 + if err := godotenv.Overload(); err != nil { + log.Fatalf(".env 파일을 로드할 수 없습니다: %v", err) + } +} + +func getEnvOrPanic(key string) string { + value, exists := os.LookupEnv(key) + if !exists || value == "" { + log.Fatalf("필수 환경 변수 %s가 설정되지 않았습니다.", key) + } + return value +} + // maskAPIKey 는 로그에 노출할 때 클라이언트 API Key 를 일부만 보여주기 위한 헬퍼입니다. func maskAPIKey(key string) string { if len(key) <= 8 { @@ -36,6 +54,30 @@ func firstNonEmpty(values ...string) string { func main() { logger := logging.NewStdJSONLogger("client") + // .env 파일 로드 + if err := godotenv.Load(); err != nil { + log.Println("Failed to load .env file. Using default environment variables.") + } + + // 필수 환경 변수 유효성 검사 + serverAddr := getEnvOrPanic("HOP_CLIENT_SERVER_ADDR") + clientDomain := getEnvOrPanic("HOP_CLIENT_DOMAIN") + apiKey := getEnvOrPanic("HOP_CLIENT_API_KEY") + localTarget := getEnvOrPanic("HOP_CLIENT_LOCAL_TARGET") + debug := getEnvOrPanic("HOP_CLIENT_DEBUG") + + // 디버깅 플래그 확인 + if debug != "true" && debug != "false" { + log.Fatalf("Invalid value for HOP_CLIENT_DEBUG. It must be set to 'true' or 'false'.") + } + + // 유효성 검사 결과 출력 + log.Printf("SERVER_ADDR: %s", serverAddr) + log.Printf("CLIENT_DOMAIN: %s", clientDomain) + log.Printf("API_KEY: %s", maskAPIKey(apiKey)) + log.Printf("LOCAL_TARGET: %s", localTarget) + log.Printf("DEBUG: %s", debug) + // CLI 인자 정의 (env 보다 우선 적용됨) serverAddrFlag := flag.String("server-addr", "", "DTLS server address (host:port)") domainFlag := flag.String("domain", "", "registered domain (e.g. api.example.com)") diff --git a/cmd/server/main.go b/cmd/server/main.go index 356cbeb..0357fec 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -8,6 +8,7 @@ import ( "fmt" "io" stdfs "io/fs" + "log" "net" "net/http" "os" @@ -28,6 +29,8 @@ import ( "github.com/dalbodeule/hop-gate/internal/observability" "github.com/dalbodeule/hop-gate/internal/protocol" "github.com/dalbodeule/hop-gate/internal/store" + + "github.com/joho/godotenv" ) type dtlsSessionWrapper struct { @@ -35,6 +38,21 @@ type dtlsSessionWrapper struct { mu sync.Mutex } +func init() { + // .env 파일 로드 + if err := godotenv.Overload(); err != nil { + log.Fatalf(".env 파일을 로드할 수 없습니다: %v", err) + } +} + +func getEnvOrPanic(key string) string { + value, exists := os.LookupEnv(key) + if !exists || value == "" { + log.Fatalf("필수 환경 변수 %s가 설정되지 않았습니다.", key) + } + return value +} + // canonicalizeDomainForDNS 는 DTLS 핸드셰이크에서 전달된 도메인 문자열을 // DNS 조회 및 DB 조회에 사용할 수 있는 정규화된 호스트명으로 변환합니다. (ko) // canonicalizeDomainForDNS normalizes the domain string from the DTLS handshake @@ -277,8 +295,8 @@ var hopGateOwnedHeaders = map[string]struct{}{ "Referrer-Policy": {}, } -// writeErrorPage 는 주요 HTTP 에러 코드(400/404/500/502/504/525)에 대해 정적 HTML 에러 페이지를 렌더링합니다. (ko) -// writeErrorPage renders static HTML error pages for key HTTP error codes (400/404/500/502/504/525). (en) +// writeErrorPage 는 주요 HTTP 에러 코드(400/404/500/525)에 대해 정적 HTML 에러 페이지를 렌더링합니다. (ko) +// writeErrorPage renders static HTML error pages for key HTTP error codes (400/404/500/525). (en) // // 템플릿 로딩 우선순위: (ko) // 1. HOP_ERROR_PAGES_DIR/.html (또는 ./errors/.html) (ko) @@ -294,31 +312,9 @@ func writeErrorPage(w http.ResponseWriter, r *http.Request, status int) { setSecurityAndIdentityHeaders(w, r) } - // 4xx / 5xx 대역에 대한 템플릿 매핑 규칙: (ko) - // - 400 series: 400.html 로 렌더링 (단, 404 는 404.html 사용) (ko) - // - 500 series: 500.html 로 렌더링 (단, 502/504/525 는 개별 템플릿 사용) (ko) - // - // Mapping rules for 4xx / 5xx ranges: (en) - // - 400 series: render using 400.html (except 404 uses 404.html). (en) - // - 500 series: render using 500.html (except 502/504/525 which have dedicated templates). (en) - mapped := status - switch { - case status >= 400 && status < 500: - if status != http.StatusBadRequest && status != http.StatusNotFound { - mapped = http.StatusBadRequest - } - case status >= 500 && status < 600: - if status != http.StatusInternalServerError && - status != http.StatusBadGateway && - status != errorpages.StatusGatewayTimeout && - status != errorpages.StatusTLSHandshakeFailed { - mapped = http.StatusInternalServerError - } - } - - // Delegates actual HTML rendering to internal/errorpages with mapped status. (en) - // 실제 HTML 렌더링은 매핑된 상태 코드로 internal/errorpages 패키지에 위임합니다. (ko) - errorpages.Render(w, r, mapped) + // Delegates actual HTML rendering to internal/errorpages. (en) + // 실제 HTML 렌더링은 internal/errorpages 패키지에 위임합니다. (ko) + errorpages.Render(w, r, status) } // setSecurityAndIdentityHeaders 는 HopGate 에서 공통으로 추가하는 보안/식별 헤더를 설정합니다. (ko) @@ -652,6 +648,30 @@ func newHTTPHandler(logger logging.Logger, proxyTimeout time.Duration) http.Hand func main() { logger := logging.NewStdJSONLogger("server") + // .env 파일 로드 + if err := godotenv.Load(); err != nil { + log.Println(".env 파일을 로드할 수 없습니다. 기본 환경 변수를 사용합니다.") + } + + // 필수 환경 변수 유효성 검사 + httpListen := getEnvOrPanic("HOP_SERVER_HTTP_LISTEN") + httpsListen := getEnvOrPanic("HOP_SERVER_HTTPS_LISTEN") + dtlsListen := getEnvOrPanic("HOP_SERVER_DTLS_LISTEN") + domain := getEnvOrPanic("HOP_SERVER_DOMAIN") + debug := getEnvOrPanic("HOP_SERVER_DEBUG") + + // 디버깅 플래그 확인 + if debug != "true" && debug != "false" { + log.Fatalf("HOP_SERVER_DEBUG 값이 잘못되었습니다. true 또는 false로 설정해야 합니다.") + } + + // 유효성 검사 결과 출력 + log.Printf("HTTP_LISTEN: %s", httpListen) + log.Printf("HTTPS_LISTEN: %s", httpsListen) + log.Printf("DTLS_LISTEN: %s", dtlsListen) + log.Printf("DOMAIN: %s", domain) + log.Printf("DEBUG: %s", debug) + // Prometheus 메트릭 등록 observability.MustRegister() diff --git a/go.mod b/go.mod index d6ee597..9223dea 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( entgo.io/ent v0.14.5 github.com/go-acme/lego/v4 v4.28.1 github.com/google/uuid v1.6.0 + github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 github.com/pion/dtls/v3 v3.0.7 github.com/prometheus/client_golang v1.19.0 diff --git a/go.sum b/go.sum index cfe1a69..2c9b84c 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo= github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=