mirror of
https://github.com/dalbodeule/hop-gate.git
synced 2025-12-09 13:25:44 +09:00
[refactor](server, client, config): remove godotenv dependency and enhance env var handling
- Replaced `godotenv` with a custom `.env` loader that respects OS-level environment variables. - Updated server and client initialization to prioritize OS environment variables over `.env` values. - Improved environment variable validation and logging with structured logs. - Applied cleaner error handling and removed redundant `log` package usage.
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -14,21 +13,15 @@ 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 {
|
||||
func getEnvOrPanic(logger logging.Logger, key string) string {
|
||||
value, exists := os.LookupEnv(key)
|
||||
if !exists || value == "" {
|
||||
log.Fatalf("필수 환경 변수 %s가 설정되지 않았습니다.", key)
|
||||
if !exists || strings.TrimSpace(value) == "" {
|
||||
logger.Error("missing required environment variable", logging.Fields{
|
||||
"env": key,
|
||||
})
|
||||
os.Exit(1)
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -54,29 +47,40 @@ 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.")
|
||||
// 1. 환경변수(.env 포함)에서 클라이언트 설정 로드
|
||||
// internal/config 패키지가 .env 를 먼저 읽고, 이미 설정된 OS 환경변수를 우선시합니다.
|
||||
envCfg, err := config.LoadClientConfigFromEnv()
|
||||
if err != nil {
|
||||
logger.Error("failed to load client config from env", logging.Fields{
|
||||
"error": err.Error(),
|
||||
})
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 필수 환경 변수 유효성 검사
|
||||
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")
|
||||
// 2. 필수 환경 변수 유효성 검사 (.env 포함; OS 환경변수가 우선)
|
||||
serverAddrEnv := getEnvOrPanic(logger, "HOP_CLIENT_SERVER_ADDR")
|
||||
clientDomainEnv := getEnvOrPanic(logger, "HOP_CLIENT_DOMAIN")
|
||||
apiKeyEnv := getEnvOrPanic(logger, "HOP_CLIENT_API_KEY")
|
||||
localTargetEnv := getEnvOrPanic(logger, "HOP_CLIENT_LOCAL_TARGET")
|
||||
debugEnv := getEnvOrPanic(logger, "HOP_CLIENT_DEBUG")
|
||||
|
||||
// 디버깅 플래그 확인
|
||||
if debug != "true" && debug != "false" {
|
||||
log.Fatalf("Invalid value for HOP_CLIENT_DEBUG. It must be set to 'true' or 'false'.")
|
||||
// 디버깅 플래그 형식 확인
|
||||
if debugEnv != "true" && debugEnv != "false" {
|
||||
logger.Error("invalid value for HOP_CLIENT_DEBUG; must be 'true' or 'false'", logging.Fields{
|
||||
"env": "HOP_CLIENT_DEBUG",
|
||||
"value": debugEnv,
|
||||
})
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 유효성 검사 결과 출력
|
||||
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)
|
||||
// 유효성 검사 결과를 구조화 로그로 출력
|
||||
logger.Info("validated client env vars", logging.Fields{
|
||||
"HOP_CLIENT_SERVER_ADDR": serverAddrEnv,
|
||||
"HOP_CLIENT_DOMAIN": clientDomainEnv,
|
||||
"HOP_CLIENT_API_KEY_MASK": maskAPIKey(apiKeyEnv),
|
||||
"HOP_CLIENT_LOCAL_TARGET": localTargetEnv,
|
||||
"HOP_CLIENT_DEBUG": debugEnv,
|
||||
})
|
||||
|
||||
// CLI 인자 정의 (env 보다 우선 적용됨)
|
||||
serverAddrFlag := flag.String("server-addr", "", "DTLS server address (host:port)")
|
||||
@@ -86,15 +90,6 @@ func main() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// 1. 환경변수(.env 포함)에서 클라이언트 설정 로드
|
||||
envCfg, err := config.LoadClientConfigFromEnv()
|
||||
if err != nil {
|
||||
logger.Error("failed to load client config from env", logging.Fields{
|
||||
"error": err.Error(),
|
||||
})
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 2. CLI 인자 우선, env 후순위로 최종 설정 구성
|
||||
finalCfg := &config.ClientConfig{
|
||||
ServerAddr: firstNonEmpty(strings.TrimSpace(*serverAddrFlag), strings.TrimSpace(envCfg.ServerAddr)),
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
stdfs "io/fs"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -29,8 +28,6 @@ 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 {
|
||||
@@ -38,17 +35,13 @@ 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 {
|
||||
func getEnvOrPanic(logger logging.Logger, key string) string {
|
||||
value, exists := os.LookupEnv(key)
|
||||
if !exists || value == "" {
|
||||
log.Fatalf("필수 환경 변수 %s가 설정되지 않았습니다.", key)
|
||||
if !exists || strings.TrimSpace(value) == "" {
|
||||
logger.Error("missing required environment variable", logging.Fields{
|
||||
"env": key,
|
||||
})
|
||||
os.Exit(1)
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -648,34 +641,8 @@ 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()
|
||||
|
||||
// 1. 서버 설정 로드 (.env + 환경변수)
|
||||
// internal/config 패키지가 .env 를 먼저 읽고, 이미 설정된 OS 환경변수를 우선시합니다.
|
||||
cfg, err := config.LoadServerConfigFromEnv()
|
||||
if err != nil {
|
||||
logger.Error("failed to load server config from env", logging.Fields{
|
||||
@@ -684,6 +651,34 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 2. 필수 환경 변수 유효성 검사 (.env 포함; OS 환경변수가 우선)
|
||||
httpListenEnv := getEnvOrPanic(logger, "HOP_SERVER_HTTP_LISTEN")
|
||||
httpsListenEnv := getEnvOrPanic(logger, "HOP_SERVER_HTTPS_LISTEN")
|
||||
dtlsListenEnv := getEnvOrPanic(logger, "HOP_SERVER_DTLS_LISTEN")
|
||||
domainEnv := getEnvOrPanic(logger, "HOP_SERVER_DOMAIN")
|
||||
debugEnv := getEnvOrPanic(logger, "HOP_SERVER_DEBUG")
|
||||
|
||||
// 디버깅 플래그 형식 확인
|
||||
if debugEnv != "true" && debugEnv != "false" {
|
||||
logger.Error("invalid value for HOP_SERVER_DEBUG; must be 'true' or 'false'", logging.Fields{
|
||||
"env": "HOP_SERVER_DEBUG",
|
||||
"value": debugEnv,
|
||||
})
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 유효성 검사 결과를 구조화 로그로 출력
|
||||
logger.Info("validated server env vars", logging.Fields{
|
||||
"HOP_SERVER_HTTP_LISTEN": httpListenEnv,
|
||||
"HOP_SERVER_HTTPS_LISTEN": httpsListenEnv,
|
||||
"HOP_SERVER_DTLS_LISTEN": dtlsListenEnv,
|
||||
"HOP_SERVER_DOMAIN": domainEnv,
|
||||
"HOP_SERVER_DEBUG": debugEnv,
|
||||
})
|
||||
|
||||
// Prometheus 메트릭 등록
|
||||
observability.MustRegister()
|
||||
|
||||
logger.Info("hop-gate server starting", logging.Fields{
|
||||
"stack": "prometheus-loki-grafana",
|
||||
"http_listen": cfg.HTTPListen,
|
||||
|
||||
@@ -107,7 +107,11 @@ func loadDotEnvOnce() {
|
||||
val = strings.Trim(val, `"'`)
|
||||
|
||||
if key != "" {
|
||||
_ = os.Setenv(key, val)
|
||||
// 이미 OS 환경변수에 설정된 값이 있는 경우 이를 우선시하고,
|
||||
// 비어 있는 키에 대해서만 .env 값을 주입합니다.
|
||||
if _, exists := os.LookupEnv(key); !exists {
|
||||
_ = os.Setenv(key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
@@ -209,7 +213,8 @@ func loadLoggingFromEnv() LoggingConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// LoadServerConfigFromEnv 는 .env 를 우선 읽고, 이후 환경 변수를 기반으로 서버 설정을 구성합니다.
|
||||
// LoadServerConfigFromEnv 는 .env 를 한 번 읽어 현재 환경변수를 보완한 뒤
|
||||
// "환경변수 > .env" 우선순위로 서버 설정을 구성합니다.
|
||||
func LoadServerConfigFromEnv() (*ServerConfig, error) {
|
||||
loadDotEnvOnce()
|
||||
if dotenvErr != nil {
|
||||
@@ -228,7 +233,8 @@ func LoadServerConfigFromEnv() (*ServerConfig, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// LoadClientConfigFromEnv 는 .env 를 우선 읽고, 이후 환경 변수를 기반으로 클라이언트 설정을 구성합니다.
|
||||
// LoadClientConfigFromEnv 는 .env 를 한 번 읽어 현재 환경변수를 보완한 뒤
|
||||
// "환경변수 > .env" 우선순위로 클라이언트 설정을 구성합니다.
|
||||
// 실제 런타임에서 사용되는 필드는 ServerAddr, Domain, ClientAPIKey, LocalTarget 입니다.
|
||||
func LoadClientConfigFromEnv() (*ClientConfig, error) {
|
||||
loadDotEnvOnce()
|
||||
|
||||
Reference in New Issue
Block a user