From 77a08f1c55b8efebf4e78a0fc08d8f03e5aa65bd Mon Sep 17 00:00:00 2001 From: xucheng Date: Wed, 14 Jan 2026 11:12:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Docker=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=B9=B6=E6=B7=BB=E5=8A=A0=20mTLS=20=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile: 添加 Python 虚拟环境,修复端口匹配,跳过 SSL 验证下载 Portwarden - entrypoint.sh: 支持 http:// 地址(用于测试) - frontend/src/lib/api.ts: 添加 getReport 函数 - frontend/next.config.ts: 移除无效的 turbopack 配置 - frontend/src/app/page.tsx: 添加 Suspense 边界包裹 useSearchParams - frontend/src/components/nav-header.tsx: 添加 Suspense 边界包裹 useSearchParams - bastian/: 添加从 lyman.p12 提取的 mTLS 证书文件 --- Dockerfile | 19 +++++++-------- bastian/client.crt | 23 ++++++++++++++++++ bastian/client.key | 32 ++++++++++++++++++++++++++ entrypoint.sh | 14 ++++------- frontend/next.config.ts | 10 -------- frontend/package-lock.json | 17 +------------- frontend/src/app/page.tsx | 17 ++++++++++++-- frontend/src/components/nav-header.tsx | 22 ++++++++++++++++-- frontend/src/lib/api.ts | 9 ++++++++ package-lock.json | 6 +++++ 10 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 bastian/client.crt create mode 100644 bastian/client.key create mode 100644 package-lock.json diff --git a/Dockerfile b/Dockerfile index ccf8441..f75d076 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,14 +33,16 @@ RUN apt-get update && apt-get install -y \ WORKDIR /app -# 2. Install Python Backend Dependencies +# 2. Create Python Virtual Environment and Install Dependencies COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +RUN python -m venv /app/.venv && \ + /app/.venv/bin/pip install --no-cache-dir -r requirements.txt # 3. Bake in Portwarden Client (The "Tunnel") # This runs during build time to download the binary into the image +# 使用 -k 跳过 SSL 证书验证(bastion.3prism.ai 证书过期) RUN echo "Downloading Portwarden Client from: ${BASTION_URL}/releases/portwardenc-${HOST_ARCH}" && \ - curl -fsSL "${BASTION_URL}/releases/portwardenc-${HOST_ARCH}" -o /usr/local/bin/portwardenc && \ + curl -fsSLk "${BASTION_URL}/releases/portwardenc-${HOST_ARCH}" -o /usr/local/bin/portwardenc && \ chmod +x /usr/local/bin/portwardenc # 4. Copy Frontend Build Artifacts @@ -64,7 +66,7 @@ COPY entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/entrypoint.sh ./start_app.sh # Environment Variables Defaults -ENV PW_LOCAL_PORT=3000 +ENV PW_LOCAL_PORT=3001 # Disable Next.js Telemetry ENV NEXT_TELEMETRY_DISABLED=1 @@ -74,8 +76,7 @@ ENV NEXT_TELEMETRY_DISABLED=1 # Entrypoint & Command ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] -# We need a robust start script for prod. -# For now, we'll use a modified start command inline or assume start_app.sh is smart enough. -# Let's override the default start_app.sh behavior to use production modes if possible, -# OR just keep it simple as user requested "built-in". -CMD ["bash", "-c", "cd frontend && npm start & cd backend && uvicorn app.main:app --host 0.0.0.0 --port 8000"] +# 启动前端 (Next.js 生产模式) 和后端 (FastAPI) +# 前端监听 3001 端口,后端监听 8000 端口 +# 使用虚拟环境中的 Python 运行后端 +CMD ["bash", "-c", "cd /app/frontend && npm start & cd /app/backend && /app/.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 && wait"] diff --git a/bastian/client.crt b/bastian/client.crt new file mode 100644 index 0000000..3de605c --- /dev/null +++ b/bastian/client.crt @@ -0,0 +1,23 @@ +Bag Attributes + friendlyName: lyman + localKeyID: E6 D3 1F DC A4 9D 66 39 16 EF D1 CB EF 93 38 32 04 2B CE 2D +subject=CN = lyman +issuer=CN = Bastion Root CA, O = Bastion WebUI, C = CN +-----BEGIN CERTIFICATE----- +MIICzzCCAnWgAwIBAgIBBzAKBggqhkjOPQQDAjA/MRgwFgYDVQQDDA9CYXN0aW9u +IFJvb3QgQ0ExFjAUBgNVBAoMDUJhc3Rpb24gV2ViVUkxCzAJBgNVBAYTAkNOMB4X +DTI1MTAyNzEzNDEzN1oXDTI2MTAyNzEzNDEzN1owEDEOMAwGA1UEAwwFbHltYW4w +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxhcnS/u1xu8s8/clsjTGX +PzbL7o/QPwDF8UY/4CpCLSA2fY32cq3bFS4qez+npdj2Nw+/+mWrewZo+qJwqkwk +qpAR2Mqxm6vZw7q8uXnM+aYyCnpHxy2QZz55+Ro0RP/HSRcBOtw98YGa2GWIy9cZ +UMgYovU8JhHBmdXs8WNntIwKWmEwXoAbQ64rqWrceuaam9BH1FzcHbrQFdKbYW1b +wt67VeQ8B6B1ZCQ038mV3DdAIsJ60YTCN3yEilD0EIvtJ7mQJCoMNfRwcNGy1eX8 +KCKqyhhpCp9sAvVvAsU7lR8pY+E8FbNTMhwATT43Y01G5lUE996OSjJYnkCW1XHB +AgMBAAGjgcUwgcIwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBaAwMwYJYIZI +AYb4QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIENsaWVudCBDZXJ0aWZpY2F0ZTAd +BgNVHQ4EFgQUJDeldPz+ebTsSLc7adRbf0j7kBswHwYDVR0jBBgwFoAU7en/aolT +LTt2LpEBIYMcIWxJ//owDgYDVR0PAQH/BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUF +BwMCBggrBgEFBQcDBDAKBggqhkjOPQQDAgNIADBFAiEAhC2pz7QFPcTHgi2FBGXj +zDWwbkSGQXvN2EsVmuhL5XMCIBskor+/7TfJuU0YSvuCO7nD92Ys3qlH5rfL1PSE +4dba +-----END CERTIFICATE----- diff --git a/bastian/client.key b/bastian/client.key new file mode 100644 index 0000000..6470b57 --- /dev/null +++ b/bastian/client.key @@ -0,0 +1,32 @@ +Bag Attributes + friendlyName: lyman + localKeyID: E6 D3 1F DC A4 9D 66 39 16 EF D1 CB EF 93 38 32 04 2B CE 2D +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxhcnS/u1xu8s8 +/clsjTGXPzbL7o/QPwDF8UY/4CpCLSA2fY32cq3bFS4qez+npdj2Nw+/+mWrewZo ++qJwqkwkqpAR2Mqxm6vZw7q8uXnM+aYyCnpHxy2QZz55+Ro0RP/HSRcBOtw98YGa +2GWIy9cZUMgYovU8JhHBmdXs8WNntIwKWmEwXoAbQ64rqWrceuaam9BH1FzcHbrQ +FdKbYW1bwt67VeQ8B6B1ZCQ038mV3DdAIsJ60YTCN3yEilD0EIvtJ7mQJCoMNfRw +cNGy1eX8KCKqyhhpCp9sAvVvAsU7lR8pY+E8FbNTMhwATT43Y01G5lUE996OSjJY +nkCW1XHBAgMBAAECggEABydWqi/6Sk4jUGRt5qKRdCXOBogzvNRJyrrlxhruz67L +w0+1BfCE3kUIxgQ5slQsotkLmM9FY9Rv3juJtSZ9rfAUIRimVrjTgzw1P8KYfI4L +uGzuP3XQrvF5exboALbOQ7NSWqBgN0b5SQUYTAZigdYHfDZWkXtmFYdxGhdiS7pc +P31RroXLxyWpwKHmuxtfp4r8Hz3hSGEWSb978hTyWL7jkXftcICirxMk8ehC/WU9 +KaDNfk6E1BjaA8p/qM5H52iGVwYWIxDgkh2w00ntL1mpG7YxoHyrZqPE0pIaMd6m +8kTUlcjrGbPlO1mUMcvZ2wezC5JHKSTmZwdLjQ/LmQKBgQDvr3zbOFZxyojiN2Nc +PCp6k3zkUHr8DoEaaRzyeIaCgzmEjZrFfmeS9OGdKV/51Dy5CxALuCUjuWQPQ+sw +GpgHTFr7lLGlgigGN87lBJrm8CnI+g0ejJzOZAACWhONipnynd7D7NX+ziR7JtgR +bUozhQZzuOpwSaJpuUSp1XbmOQKBgQC9mx1B3x8X2UWNUWaUbWfDC/dUBQRGfRR3 +QNzVqSwhlUT4jA6EVLHFB/jpMbY53DOH1TdgLNoP/nsZusH9OY4IiqxdvNnWn738 +mbo7hcvlTdnuZXF71SuoUCSCOaTACVXsKv6uNA/65gtxB2QFdU/yYIjrIT+wYIKW +sBvKGqgnyQKBgAebajsK7rNt2ipT17N1tWNuiug0JbMaQr6z11dau+oogArU87SQ +7nibjQ2P4pvrQIIe98NndMZNe/+ACFbegTS6F6kkbv7xwpNv8gESxFfQB4N5bDEs +BU4Hnnh0o2o6m+g3Wnqdaa1MnZvK/9CNx20bK7lAhTBLJfx1BNjfDYcBAoGBAIbA +o/V5zo5Tg2PhM0dPzgvICFo1SomSQaZTed974PppLOB8IaEY1FLUzKlnBDxw7Eqg +VT/MAJqXYQOzQEVozzHw5HmmSyeG1i6dTscY2wU35CfS/ulkYie39Yp7z0QQHnm/ +QMusAqNtNTp6ZzKd4li/FPAO7EW9AXJ47PchJNtJAoGAfYphGlDnBniUYoK4PmfP +Q0kw7EQcFC/kzFSpTNmew0Nc8cymWaglesd92GXfbYfphRB89oj8/P+27eBJ8cT5 +QQH4HoFypCPN1skBovRnW85boXnNXI0O/sIt7GUGX8yl3cT2/St+dKcM24KO3o+4 +6h8+pyp4K79FKaYJtftptWU= +-----END PRIVATE KEY----- diff --git a/entrypoint.sh b/entrypoint.sh index 362da1a..c542f09 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -63,19 +63,15 @@ if [ -z "$PW_SERVICE_ID" ] || [ -z "$PW_SERVER_ADDRS" ] || [ -z "$PW_LOCAL_PORT" exit 1 fi -# 拒绝 http://;要求 https://;且不允许包含路径 /portwarden(客户端会自动添加) -if echo "$PW_SERVER_ADDRS" | grep -q "http://"; then - echo "Error: PW_SERVER_ADDRS 中包含 http:// ,仅支持 https:// 地址。" >&2 - exit 1 -fi -if ! echo "$PW_SERVER_ADDRS" | grep -q "https://"; then - echo "Error: PW_SERVER_ADDRS 必须全部为 https:// 地址,多个地址用逗号分隔。" >&2 - exit 1 -fi +# 验证格式:支持 http:// 和 https://,但不允许包含路径 /portwarden if echo "$PW_SERVER_ADDRS" | grep -qi "/portwarden"; then echo "Error: PW_SERVER_ADDRS 不应包含路径 '/portwarden';客户端会自动添加该前缀。" >&2 exit 1 fi +if ! echo "$PW_SERVER_ADDRS" | grep -qE 'https?://'; then + echo "Error: PW_SERVER_ADDRS 必须为 http:// 或 https:// 地址。" >&2 + exit 1 +fi if ! echo "$PW_LOCAL_PORT" | grep -Eq '^[0-9]+$'; then echo "Error: PW_LOCAL_PORT 必须是数字端口,例如 8080。" >&2 exit 1 diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 018b5ed..fa6156f 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -9,16 +9,6 @@ const nextConfig: NextConfig = { }, ] }, - experimental: { - turbopack: { - // Explicitly set the root to the current directory (frontend) or project root - // Since the warning says it selected the parent directory as root, but found another lockfile here. - // We can try to force it to ignore the parent one by setting root to this one? - // "root": "./" - // Actually, let's just leave it alone if I am not sure. The user just showed the log. - // It seems harmless. - } - }, }; export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 318297b..5701086 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -86,7 +86,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2516,7 +2515,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2527,7 +2525,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2583,7 +2580,6 @@ "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", @@ -3089,7 +3085,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3452,7 +3447,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4153,7 +4147,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4339,7 +4332,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7571,7 +7563,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7581,7 +7572,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7594,7 +7584,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.69.0.tgz", "integrity": "sha512-yt6ZGME9f4F6WHwevrvpAjh42HMvocuSnSIHUGycBqXIJdhqGSPQzTpGF+1NLREk/58IdPxEMfPcFCjlMhclGw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -8443,8 +8432,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -8510,7 +8498,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8703,7 +8690,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9143,7 +9129,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz", "integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 45366c8..7d5cd74 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, Suspense } from "react" import { useSearchParams } from "next/navigation" import { cn } from "@/lib/utils" import { SearchStock } from "@/components/search-stock" @@ -25,7 +25,7 @@ import { StockChart } from "@/components/stock-chart" import { AiDiscussionView } from "@/components/ai-discussion-view" import { HistoryView } from "@/components/history-view" -export default function Home() { +function HomeInner() { const searchParams = useSearchParams() // 状态管理 @@ -382,3 +382,16 @@ function SearchStockWithSelection({ onSelect }: { onSelect: (c: SearchResult, s? ) } + +// Suspense wrapper for useSearchParams +export default function Home() { + return ( + + + + }> + + + ) +} diff --git a/frontend/src/components/nav-header.tsx b/frontend/src/components/nav-header.tsx index f2c4ecf..6205be1 100644 --- a/frontend/src/components/nav-header.tsx +++ b/frontend/src/components/nav-header.tsx @@ -4,7 +4,7 @@ import Link from "next/link" import { MonitorPlay } from "lucide-react" import { useRouter, useSearchParams } from "next/navigation" import { HeaderSearch } from "@/components/header-search" -import { useEffect, useState } from "react" +import { useEffect, useState, Suspense } from "react" import { Select, SelectContent, @@ -20,7 +20,7 @@ type RecentCompany = { last_update: string } -export function NavHeader() { +function NavHeaderInner() { const router = useRouter() const searchParams = useSearchParams() @@ -144,3 +144,21 @@ export function NavHeader() { ) } + +// Suspense wrapper for useSearchParams +export function NavHeader() { + return ( + +
+ + + 股票分析 AI + +
+ + }> + +
+ ) +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 34ae225..3351128 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -188,3 +188,12 @@ export async function getReports() { console.warn("getReports() is deprecated. Use getAnalysisHistory(companyId) instead.") return [] } + +/** + * 获取单个报告详情(兼容旧API) + */ +export async function getReport(reportId: number) { + const res = await fetch(`${API_BASE}/reports/${reportId}`) + if (!res.ok) throw new Error("Failed to get report") + return res.json() +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c289293 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "FA3-Datafetch", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}