mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 16:35:27 +08:00
Load .env and config.json for local go runs
This commit is contained in:
@@ -18,6 +18,10 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := config.LoadDotEnv(); err != nil {
|
||||
config.Logger.Warn("[dotenv] load failed", "error", err)
|
||||
}
|
||||
config.RefreshLogger()
|
||||
webui.EnsureBuiltOnStartup()
|
||||
_ = auth.AdminKey()
|
||||
app := server.NewApp()
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
services:
|
||||
ds2api:
|
||||
image: ghcr.io/cjackhwang/ds2api:latest
|
||||
container_name: ds2api
|
||||
restart: always
|
||||
ports:
|
||||
- "6011:5001"
|
||||
volumes:
|
||||
- ./config.json:/app/config.json # 配置文件
|
||||
- ./.env:/app/.env # 环境变量
|
||||
container_name: ds2api
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${DS2API_HOST_PORT:-6011}:${PORT:-5001}"
|
||||
volumes:
|
||||
- ./config.json:/app/config.json # 配置文件
|
||||
- ./.env:/app/.env # 环境变量
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- LOG_LEVEL=INFO
|
||||
|
||||
81
internal/config/dotenv.go
Normal file
81
internal/config/dotenv.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LoadDotEnv loads environment variables from .env in the current working
|
||||
// directory without overriding variables that are already set.
|
||||
func LoadDotEnv() error {
|
||||
return loadDotEnvFromPath(filepath.Join(BaseDir(), ".env"))
|
||||
}
|
||||
|
||||
func loadDotEnvFromPath(path string) error {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.ReplaceAll(string(content), "\r\n", "\n"), "\n")
|
||||
for i, rawLine := range lines {
|
||||
line := strings.TrimSpace(rawLine)
|
||||
if i == 0 {
|
||||
line = strings.TrimPrefix(line, "\ufeff")
|
||||
}
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "export ") {
|
||||
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
|
||||
}
|
||||
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
return fmt.Errorf("%s:%d invalid env assignment", path, i+1)
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" {
|
||||
return fmt.Errorf("%s:%d empty env key", path, i+1)
|
||||
}
|
||||
if _, exists := os.LookupEnv(key); exists {
|
||||
continue
|
||||
}
|
||||
if err := os.Setenv(key, normalizeDotEnvValue(strings.TrimSpace(value))); err != nil {
|
||||
return fmt.Errorf("%s:%d set env %q: %w", path, i+1, key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeDotEnvValue(raw string) string {
|
||||
if len(raw) < 2 {
|
||||
return raw
|
||||
}
|
||||
first := raw[0]
|
||||
last := raw[len(raw)-1]
|
||||
if (first != '"' || last != '"') && (first != '\'' || last != '\'') {
|
||||
return raw
|
||||
}
|
||||
|
||||
raw = raw[1 : len(raw)-1]
|
||||
if first == '\'' {
|
||||
return raw
|
||||
}
|
||||
|
||||
replacer := strings.NewReplacer(
|
||||
`\\`, `\`,
|
||||
`\n`, "\n",
|
||||
`\r`, "\r",
|
||||
`\t`, "\t",
|
||||
`\"`, `"`,
|
||||
)
|
||||
return replacer.Replace(raw)
|
||||
}
|
||||
83
internal/config/dotenv_test.go
Normal file
83
internal/config/dotenv_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadDotEnvLoadsWorkingDirectoryFileWithoutOverridingExistingEnv(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
oldWD, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd: %v", err)
|
||||
}
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("chdir temp dir: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = os.Chdir(oldWD)
|
||||
})
|
||||
|
||||
const newKey = "DS2API_TEST_DOTENV_NEW"
|
||||
const keepKey = "DS2API_TEST_DOTENV_KEEP"
|
||||
const quotedKey = "DS2API_TEST_DOTENV_QUOTED"
|
||||
|
||||
unsetEnv(t, newKey)
|
||||
unsetEnv(t, quotedKey)
|
||||
t.Setenv(keepKey, "from-env")
|
||||
|
||||
content := "DS2API_TEST_DOTENV_NEW=from-file\n" +
|
||||
"DS2API_TEST_DOTENV_KEEP=from-file\n" +
|
||||
"DS2API_TEST_DOTENV_QUOTED=\"line1\\nline2\"\n"
|
||||
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("write .env: %v", err)
|
||||
}
|
||||
|
||||
if err := LoadDotEnv(); err != nil {
|
||||
t.Fatalf("LoadDotEnv() error: %v", err)
|
||||
}
|
||||
|
||||
if got := os.Getenv(newKey); got != "from-file" {
|
||||
t.Fatalf("expected %s from .env, got %q", newKey, got)
|
||||
}
|
||||
if got := os.Getenv(keepKey); got != "from-env" {
|
||||
t.Fatalf("expected existing env to win, got %q", got)
|
||||
}
|
||||
if got := os.Getenv(quotedKey); got != "line1\nline2" {
|
||||
t.Fatalf("expected quoted newline decoding, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDotEnvIgnoresMissingFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
oldWD, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd: %v", err)
|
||||
}
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("chdir temp dir: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = os.Chdir(oldWD)
|
||||
})
|
||||
|
||||
if err := LoadDotEnv(); err != nil {
|
||||
t.Fatalf("expected missing .env to be ignored, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func unsetEnv(t *testing.T, key string) {
|
||||
t.Helper()
|
||||
old, had := os.LookupEnv(key)
|
||||
if err := os.Unsetenv(key); err != nil {
|
||||
t.Fatalf("unset %s: %v", key, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if had {
|
||||
_ = os.Setenv(key, old)
|
||||
return
|
||||
}
|
||||
_ = os.Unsetenv(key)
|
||||
})
|
||||
}
|
||||
@@ -23,3 +23,7 @@ func newLogger() *slog.Logger {
|
||||
h := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})
|
||||
return slog.New(h)
|
||||
}
|
||||
|
||||
func RefreshLogger() {
|
||||
Logger = newLogger()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user