feat: add support for stripping inline comments in .env files and make Docker host port configurable via DS2API_HOST_PORT

This commit is contained in:
CJACK
2026-04-04 22:30:57 +08:00
parent eab197f4d9
commit a6836455dc
9 changed files with 130 additions and 14 deletions

View File

@@ -1,5 +1,8 @@
# DS2API runtime # DS2API runtime
# Runtime listen port inside the app/container
PORT=5001 PORT=5001
# Docker Compose host port (compose only; container still listens on PORT)
DS2API_HOST_PORT=6011
LOG_LEVEL=INFO LOG_LEVEL=INFO
# Admin authentication # Admin authentication

View File

@@ -191,7 +191,7 @@ go run ./cmd/ds2api
cp .env.example .env cp .env.example .env
cp config.example.json config.json cp config.example.json config.json
# 2. 编辑 .env至少设置 DS2API_ADMIN_KEY # 2. 编辑 .env至少设置 DS2API_ADMIN_KEY;如需修改宿主机端口,可额外设置 DS2API_HOST_PORT
# DS2API_ADMIN_KEY=请替换为强密码 # DS2API_ADMIN_KEY=请替换为强密码
# 3. 启动 # 3. 启动
@@ -201,7 +201,7 @@ docker-compose up -d
docker-compose logs -f docker-compose logs -f
``` ```
默认 `docker-compose.yml` 会把宿主机 `6011` 映射到容器内的 `5001`。如果你希望直接对外暴露 `5001`,请调整 `ports` 配置。 默认 `docker-compose.yml` 会把宿主机 `6011` 映射到容器内的 `5001`。如果你希望直接对外暴露 `5001`,请设置 `DS2API_HOST_PORT=5001`(或者手动调整 `ports` 配置
更新镜像:`docker-compose up -d --build` 更新镜像:`docker-compose up -d --build`

View File

@@ -191,7 +191,7 @@ Default URL: `http://localhost:5001`
cp .env.example .env cp .env.example .env
cp config.example.json config.json cp config.example.json config.json
# 2. Edit .env (at least set DS2API_ADMIN_KEY) # 2. Edit .env (at least set DS2API_ADMIN_KEY; optionally set DS2API_HOST_PORT to change the host port)
# DS2API_ADMIN_KEY=replace-with-a-strong-secret # DS2API_ADMIN_KEY=replace-with-a-strong-secret
# 3. Start # 3. Start
@@ -201,7 +201,7 @@ docker-compose up -d
docker-compose logs -f docker-compose logs -f
``` ```
The default `docker-compose.yml` maps host port `6011` to container port `5001`. If you want `5001` exposed directly, adjust the `ports` mapping. The default `docker-compose.yml` maps host port `6011` to container port `5001`. If you want `5001` exposed directly, set `DS2API_HOST_PORT=5001` (or adjust the `ports` mapping).
Rebuild after updates: `docker-compose up -d --build` Rebuild after updates: `docker-compose up -d --build`

View File

@@ -16,7 +16,8 @@ services:
container_name: ds2api-dev container_name: ds2api-dev
command: ["go", "run", "./cmd/ds2api"] command: ["go", "run", "./cmd/ds2api"]
ports: ports:
- "${PORT:-5001}:${PORT:-5001}" # Host port is configurable via DS2API_HOST_PORT; container port stays fixed at 5001.
- "${DS2API_HOST_PORT:-6011}:5001"
env_file: env_file:
- .env - .env
environment: environment:

View File

@@ -6,11 +6,11 @@ services:
env_file: env_file:
- .env - .env
ports: ports:
- "${DS2API_HOST_PORT:-6011}:${PORT:-5001}" # Host port is configurable via DS2API_HOST_PORT; container port stays fixed at 5001.
- "${DS2API_HOST_PORT:-6011}:5001"
volumes: volumes:
- ./config.json:/app/config.json # 配置文件 - ./config.json:/app/config.json # 配置文件
- ./.env:/app/.env # 环境变量 environment:
environment: - TZ=Asia/Shanghai
- TZ=Asia/Shanghai - LOG_LEVEL=INFO
- LOG_LEVEL=INFO - DS2API_ADMIN_KEY=${DS2API_ADMIN_KEY:-ds2api}
- DS2API_ADMIN_KEY=${DS2API_ADMIN_KEY:-ds2api}

View File

@@ -117,6 +117,8 @@ cp config.example.json config.json
# Edit .env and set at least: # Edit .env and set at least:
# DS2API_ADMIN_KEY=your-admin-key # DS2API_ADMIN_KEY=your-admin-key
# Optionally set the host port:
# DS2API_HOST_PORT=6011
# Start # Start
docker-compose up -d docker-compose up -d
@@ -125,7 +127,7 @@ docker-compose up -d
docker-compose logs -f docker-compose logs -f
``` ```
The default `docker-compose.yml` maps host port `6011` to container port `5001`. If you want `5001` exposed directly, adjust the `ports` mapping. The default `docker-compose.yml` maps host port `6011` to container port `5001`. If you want `5001` exposed directly, set `DS2API_HOST_PORT=5001` (or adjust the `ports` mapping).
### 2.2 Update ### 2.2 Update

View File

@@ -117,6 +117,8 @@ cp config.example.json config.json
# 编辑 .env请改成你的强密码至少设置 # 编辑 .env请改成你的强密码至少设置
# DS2API_ADMIN_KEY=your-admin-key # DS2API_ADMIN_KEY=your-admin-key
# 如需修改宿主机端口,可额外设置:
# DS2API_HOST_PORT=6011
# 启动 # 启动
docker-compose up -d docker-compose up -d
@@ -125,7 +127,7 @@ docker-compose up -d
docker-compose logs -f docker-compose logs -f
``` ```
默认 `docker-compose.yml` 会把宿主机 `6011` 映射到容器内的 `5001`。如果你希望直接对外暴露 `5001`,请调整 `ports` 配置。 默认 `docker-compose.yml` 会把宿主机 `6011` 映射到容器内的 `5001`。如果你希望直接对外暴露 `5001`,请设置 `DS2API_HOST_PORT=5001`(或者手动调整 `ports` 配置
### 2.2 更新 ### 2.2 更新

View File

@@ -47,7 +47,7 @@ func loadDotEnvFromPath(path string) error {
if _, exists := os.LookupEnv(key); exists { if _, exists := os.LookupEnv(key); exists {
continue continue
} }
if err := os.Setenv(key, normalizeDotEnvValue(strings.TrimSpace(value))); err != nil { if err := os.Setenv(key, normalizeDotEnvValue(trimDotEnvValue(strings.TrimSpace(value)))); err != nil {
return fmt.Errorf("%s:%d set env %q: %w", path, i+1, key, err) return fmt.Errorf("%s:%d set env %q: %w", path, i+1, key, err)
} }
} }
@@ -55,6 +55,62 @@ func loadDotEnvFromPath(path string) error {
return nil return nil
} }
// Preserve quoted values, but drop Compose-style inline comments from unquoted values.
func trimDotEnvValue(raw string) string {
if raw == "" {
return raw
}
switch raw[0] {
case '"':
if trimmed, ok := trimQuotedDotEnvValue(raw, '"'); ok {
return trimmed
}
case '\'':
if trimmed, ok := trimQuotedDotEnvValue(raw, '\''); ok {
return trimmed
}
default:
if idx := inlineDotEnvCommentStart(raw); idx >= 0 {
return strings.TrimSpace(raw[:idx])
}
}
return raw
}
func trimQuotedDotEnvValue(raw string, quote byte) (string, bool) {
escaped := false
for i := 1; i < len(raw); i++ {
ch := raw[i]
if quote == '"' && escaped {
escaped = false
continue
}
if quote == '"' && ch == '\\' {
escaped = true
continue
}
if ch == quote {
return strings.TrimSpace(raw[:i+1]), true
}
}
return raw, false
}
func inlineDotEnvCommentStart(raw string) int {
for i := 1; i < len(raw); i++ {
if raw[i] == '#' && isDotEnvCommentSpacer(raw[i-1]) {
return i
}
}
return -1
}
func isDotEnvCommentSpacer(b byte) bool {
return b == ' ' || b == '\t'
}
func normalizeDotEnvValue(raw string) string { func normalizeDotEnvValue(raw string) string {
if len(raw) < 2 { if len(raw) < 2 {
return raw return raw

View File

@@ -3,6 +3,7 @@ package config
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
) )
@@ -67,6 +68,57 @@ func TestLoadDotEnvIgnoresMissingFile(t *testing.T) {
} }
} }
func TestLoadDotEnvStripsInlineCommentsFromUnquotedValues(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 plainKey = "DS2API_TEST_DOTENV_PLAIN"
const hashKey = "DS2API_TEST_DOTENV_HASH"
const quotedKey = "DS2API_TEST_DOTENV_QUOTED_COMMENT"
const exportKey = "DS2API_TEST_DOTENV_EXPORT"
unsetEnv(t, plainKey)
unsetEnv(t, hashKey)
unsetEnv(t, quotedKey)
unsetEnv(t, exportKey)
content := strings.Join([]string{
plainKey + "=5001 # local",
hashKey + "=5001#local",
quotedKey + `="5001 # local" # keep the inner hash`,
"export " + exportKey + "=enabled # exported",
}, "\n") + "\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(plainKey); got != "5001" {
t.Fatalf("expected inline comment to be stripped, got %q", got)
}
if got := os.Getenv(hashKey); got != "5001#local" {
t.Fatalf("expected hash without preceding whitespace to remain, got %q", got)
}
if got := os.Getenv(quotedKey); got != "5001 # local" {
t.Fatalf("expected quoted value to preserve hash text, got %q", got)
}
if got := os.Getenv(exportKey); got != "enabled" {
t.Fatalf("expected export syntax to load, got %q", got)
}
}
func unsetEnv(t *testing.T, key string) { func unsetEnv(t *testing.T, key string) {
t.Helper() t.Helper()
old, had := os.LookupEnv(key) old, had := os.LookupEnv(key)