From 0c782407f5842c4ced883e65942a1bc62f122b10 Mon Sep 17 00:00:00 2001 From: RinZ27 <222222878+RinZ27@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:49:54 +0700 Subject: [PATCH] build: improve Docker robustness and fix potential security issues --- Dockerfile | 12 ++++++---- cmd/ds2api/main.go | 5 +++-- start.mjs | 56 ++++++++++++++++++++++++---------------------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/Dockerfile b/Dockerfile index ac062f7..d5113f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,8 @@ FROM debian:bookworm-slim AS runtime-base WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates \ + && groupadd -r ds2api && useradd -r -g ds2api -d /app -s /sbin/nologin ds2api \ + && mkdir -p /app/data && chown -R ds2api:ds2api /app \ && rm -rf /var/lib/apt/lists/* COPY --from=busybox-tools /bin/busybox /usr/local/bin/busybox EXPOSE 5001 @@ -36,8 +38,9 @@ CMD ["/usr/local/bin/ds2api"] FROM runtime-base AS runtime-from-source COPY --from=go-builder /out/ds2api /usr/local/bin/ds2api -COPY --from=go-builder /app/config.example.json /app/config.example.json -COPY --from=webui-builder /app/static/admin /app/static/admin +COPY --from=go-builder --chown=ds2api:ds2api /app/config.example.json /app/config.example.json +COPY --from=webui-builder --chown=ds2api:ds2api /app/static/admin /app/static/admin +USER ds2api FROM busybox-tools AS dist-extract ARG TARGETARCH @@ -60,7 +63,8 @@ RUN set -eux; \ FROM runtime-base AS runtime-from-dist COPY --from=dist-extract /out/ds2api /usr/local/bin/ds2api -COPY --from=dist-extract /out/config.example.json /app/config.example.json -COPY --from=dist-extract /out/static/admin /app/static/admin +COPY --from=dist-extract --chown=ds2api:ds2api /out/config.example.json /app/config.example.json +COPY --from=dist-extract --chown=ds2api:ds2api /out/static/admin /app/static/admin +USER ds2api FROM runtime-from-source AS final diff --git a/cmd/ds2api/main.go b/cmd/ds2api/main.go index a081a48..a4ba77e 100644 --- a/cmd/ds2api/main.go +++ b/cmd/ds2api/main.go @@ -35,8 +35,9 @@ func main() { } srv := &http.Server{ - Addr: "0.0.0.0:" + port, - Handler: app.Router, + Addr: "0.0.0.0:" + port, + Handler: app.Router, + ReadHeaderTimeout: 5 * time.Second, } localURL := fmt.Sprintf("http://127.0.0.1:%s", port) lanIP := detectLANIPv4() diff --git a/start.mjs b/start.mjs index 7f037be..c335085 100644 --- a/start.mjs +++ b/start.mjs @@ -126,9 +126,12 @@ function binaryExists() { // 查找占用端口的进程 PID function findPidByPort(port) { + const numericPort = parseInt(port, 10); + if (isNaN(numericPort)) return []; + try { if (isWindows) { - const output = execSync(`netstat -ano | findstr :${port} | findstr LISTENING`, { + const output = execSync(`netstat -ano | findstr :${numericPort} | findstr LISTENING`, { encoding: 'utf-8', shell: true, stdio: ['pipe', 'pipe', 'ignore'], @@ -141,7 +144,7 @@ function findPidByPort(port) { } return [...pids]; } else { - const output = execSync(`lsof -ti :${port}`, { + const output = execSync(`lsof -ti :${numericPort}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'], }); @@ -217,7 +220,7 @@ async function installFrontendDeps() { const proc = spawn('npm', ['ci', '--registry', MIRRORS.npm], { cwd: CONFIG.webuiDir, stdio: 'inherit', - shell: true, + shell: isWindows, }); proc.on('close', code => code === 0 ? resolve() : reject(new Error('前端依赖安装失败'))); }); @@ -239,7 +242,7 @@ async function buildBackend() { const proc = spawn('go', ['build', '-o', BINARY, './cmd/ds2api'], { cwd: __dirname, stdio: 'inherit', - shell: true, + shell: isWindows, env: { ...process.env, GOPROXY: MIRRORS.goproxy }, }); proc.on('close', code => code === 0 ? resolve() : reject(new Error('后端编译失败'))); @@ -257,22 +260,21 @@ async function buildWebui() { return new Promise((resolve, reject) => { const proc = spawn( 'npm', ['run', 'build', '--', '--outDir', CONFIG.staticAdminDir, '--emptyOutDir'], - { cwd: CONFIG.webuiDir, stdio: 'inherit', shell: true } + { cwd: CONFIG.webuiDir, stdio: 'inherit', shell: isWindows } ); proc.on('close', code => code === 0 ? resolve() : reject(new Error('前端构建失败'))); }); } // 启动后端(开发模式:go run,无需预编译) -async function startBackendDev() { - if (!checkGo()) throw new Error('未找到 Go,请先安装 Go (https://go.dev/dl/)'); - log.info(`启动后端(go run)... 本地 http://127.0.0.1:${CONFIG.port} 绑定 0.0.0.0:${CONFIG.port}`); - const proc = spawn('go', ['run', './cmd/ds2api'], { +async function startBackendDev() { + if (!checkGo()) throw new Error('未找到 Go,请先安装 Go (https://go.dev/dl/)'); + log.info(`启动后端(go run)... 本地 http://127.0.0.1:${CONFIG.port} 绑定 0.0.0.0:${CONFIG.port}`); + const proc = spawn('go', ['run', './cmd/ds2api'], { cwd: __dirname, stdio: 'inherit', - shell: true, - env: { - ...process.env, + shell: isWindows, + env: { ...process.env, PORT: CONFIG.port, LOG_LEVEL: CONFIG.logLevel, DS2API_ADMIN_KEY: CONFIG.adminKey, @@ -284,13 +286,13 @@ async function startBackendDev() { } // 启动后端(生产模式:运行编译好的二进制) -async function startBackendProd() { - if (!binaryExists()) { - log.warn('未找到编译产物,正在编译...'); - await buildBackend(); - } - log.info(`启动后端(二进制)... 本地 http://127.0.0.1:${CONFIG.port} 绑定 0.0.0.0:${CONFIG.port}`); - const proc = spawn(BINARY, [], { +async function startBackendProd() { + if (!binaryExists()) { + log.warn('未找到编译产物,正在编译...'); + await buildBackend(); + } + log.info(`启动后端(二进制)... 本地 http://127.0.0.1:${CONFIG.port} 绑定 0.0.0.0:${CONFIG.port}`); + const proc = spawn(BINARY, [], { cwd: __dirname, stdio: 'inherit', shell: false, @@ -323,14 +325,14 @@ async function startFrontend() { } // 显示状态信息 -function showStatus() { - console.log('\n' + '─'.repeat(50)); - log.success(`后端 API: http://127.0.0.1:${CONFIG.port}`); - log.success(`管理界面: http://127.0.0.1:${CONFIG.port}/admin`); - log.info(`后端绑定: 0.0.0.0:${CONFIG.port} (可通过局域网 IP 访问)`); - if (existsSync(CONFIG.webuiDir)) { - log.success(`前端 Dev: http://localhost:${CONFIG.frontendPort}`); - } +function showStatus() { + console.log('\n' + '─'.repeat(50)); + log.success(`后端 API: http://127.0.0.1:${CONFIG.port}`); + log.success(`管理界面: http://127.0.0.1:${CONFIG.port}/admin`); + log.info(`后端绑定: 0.0.0.0:${CONFIG.port} (可通过局域网 IP 访问)`); + if (existsSync(CONFIG.webuiDir)) { + log.success(`前端 Dev: http://localhost:${CONFIG.frontendPort}`); + } console.log('─'.repeat(50)); log.info('按 Ctrl+C 停止所有服务\n'); }