mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-01 23:15:27 +08:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 更新
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user