WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+233
View File
@@ -0,0 +1,233 @@
# Optional dedicated embed origin — uncomment and set server_name (e.g. embed.example.com).
# Serve only embed.html + static assets; proxy /api to the WeKnora backend.
# See docs/embed-subdomain.md
#
# server {
# listen 80;
# server_name embed.example.com;
# client_max_body_size ${MAX_FILE_SIZE};
# location = /weknora-widget.js { root /usr/share/nginx/html; }
# location ^~ /embed/ { root /usr/share/nginx/html; try_files $uri $uri/ /embed.html; }
# location ^~ /assets/ { root /usr/share/nginx/html; add_header Cache-Control "public, max-age=31536000, immutable"; }
# location /api/ { proxy_pass http://weknora-backend:8080; proxy_set_header Host $host; }
# }
map $http_x_forwarded_proto $original_scheme {
default $scheme;
https https;
http http;
}
# Classify the original request before SPA rewrites without logging its URL.
map $request_uri $ingress_route_class {
default other;
~^/api/v1/auth/me(?:\?|$) auth_me;
~^/api/v1/access/me(?:\?|$) access_me;
~^/api/v1/system/capabilities(?:\?|$) capabilities;
~^/api/v1/workbench/resources/[^/?]+(?:\?|$) resource_list;
~^/api/v1/workbench/resources/[^/?]+/[^/?]+(?:\?|$) resource_detail;
~^/api(?:/|\?|$) api;
~^/assets(?:/|\?|$) asset;
~^/tdesign-icons(?:/|\?|$) icon;
~^/embed(?:/|\?|$) embed;
~^/r(?:/|\?|$) resource_link;
~^/files(?:/|\?|$) file;
~^/config\.js(?:\?|$) config;
~^/release\.json(?:\?|$) release;
~^/weknora-widget\.js(?:\?|$) widget;
~^/(?:$|\?) shell;
~^/(?:index\.html|login|register|admin|platform)(?:/|\?|$) shell;
}
# Local clients can supply this header too; retain only a bounded ray ID.
map $http_cf_ray $ingress_cf_ray {
default "-";
"~^[0-9a-fA-F]{16,32}-[A-Z]{3}$" $http_cf_ray;
}
map $upstream_connect_time $ingress_upstream_connect_time {
default $upstream_connect_time;
"" "-";
}
map $upstream_header_time $ingress_upstream_header_time {
default $upstream_header_time;
"" "-";
}
map $upstream_response_time $ingress_upstream_response_time {
default $upstream_response_time;
"" "-";
}
log_format ingress_timing escape=json
'{"time":"$time_iso8601","route":"$ingress_route_class","status":$status,'
'"request_time":"$request_time","upstream_connect_time":"$ingress_upstream_connect_time",'
'"upstream_header_time":"$ingress_upstream_header_time","upstream_response_time":"$ingress_upstream_response_time",'
'"cf_ray":"$ingress_cf_ray"}';
server {
listen 80;
server_name localhost;
resolver 127.0.0.11 valid=10s ipv6=off;
set $app_backend ${APP_SCHEME}://${APP_HOST}:${APP_PORT};
# Default 50M, configured via MAX_FILE_SIZE_MB env var
client_max_body_size ${MAX_FILE_SIZE};
# 启用 gzip 压缩静态资源 (前端 index.js 单文件 ~1MB, 不压实测 20s+,
# 压缩后约 200-300KB, 大陆同地域低带宽机器加载从 25s 降到 3-5s)
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/css text/javascript application/javascript application/json
application/xml application/rss+xml image/svg+xml font/ttf font/otf;
# 安全头配置
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 错误日志配置
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log ingress_timing;
# Static widget loader for third-party sites
location = /weknora-widget.js {
root /usr/share/nginx/html;
add_header Cache-Control "public, max-age=3600" always;
add_header X-Content-Type-Options "nosniff" always;
}
# Internal fallback target for /embed/* routes. Keep it separate from the
# main SPA location so embed pages do not inherit X-Frame-Options.
location = /embed.html {
internal;
root /usr/share/nginx/html;
add_header Cache-Control "no-cache, must-revalidate" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
# Embed widget pages may be loaded inside third-party iframes (lightweight embed.html)
location ^~ /embed/ {
root /usr/share/nginx/html;
try_files $uri $uri/ /embed.html;
add_header Cache-Control "no-cache, must-revalidate" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
# 前端静态文件
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
# index.html 及 SPA fallback 不可缓存,否则前端升级后用户看不到新版本
add_header Cache-Control "no-cache, must-revalidate" always;
# nginx add_header 不从上层继承,需要在 location 内重复声明安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
# Runtime values can change between deployments without changing this URL.
location = /config.js {
root /usr/share/nginx/html;
try_files $uri =404;
add_header Cache-Control "no-store" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
# Change the versioned directory when updating the bundled icon assets.
location ~ ^/tdesign-icons/[0-9]+\.[0-9]+\.[0-9]+/ {
root /usr/share/nginx/html;
try_files $uri =404;
error_page 404 = @missing_asset;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
# Vite 构建产物 /assets/* 带 hash 文件名,可长期缓存
location ^~ /assets/ {
root /usr/share/nginx/html;
try_files $uri =404;
error_page 404 = @missing_asset;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
location @missing_asset {
add_header Cache-Control "no-store" always;
add_header X-Content-Type-Options "nosniff" always;
return 404;
}
# 本地存储文件代理到后端服务(用于渲染 markdown 中的图片)
# 精确匹配 /files,避免 Nginx 自动补 / 触发 301
location = /files {
proxy_pass $app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $original_scheme;
proxy_buffering off;
proxy_cache off;
add_header Cache-Control "no-store" always;
add_header X-Content-Type-Options "nosniff" always;
}
# 资源能力短链 /r/<token>:IM 渠道渲染 resource:// 图片依赖此路由,
# 缺此段会落进 SPA fallback 返回空白页,图片加载不出来。
location ^~ /r/ {
proxy_pass $app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $original_scheme;
proxy_cache off;
add_header Cache-Control "no-store" always;
add_header X-Content-Type-Options "nosniff" always;
}
# 技能 zip 上传的两条集合路由,body 上限放宽到 MAX_SKILL_BUNDLE_SIZE。
# 单独成一个 location 而不是抬高全站上限:client_max_body_size 是按
# location 生效的,写在 server 级会让知识库上传等端点也能收到技能包大小的
# body。这里放在 /api/ 之前,正则 location 优先于前缀 location 命中。
# 只匹配集合本身(POST 上传 / GET 列表),不带 /install、PATCH 等子路径:
# 那些是小 JSON,必须继续走下面 /api/ 的 MAX_FILE_SIZE。
location ~ ^/api/v1/(?:skills/catalog|sandbox-configs/[^/]+/skills)/?$ {
client_max_body_size ${MAX_SKILL_BUNDLE_SIZE};
# 正则 location 的 proxy_pass 不能带 URI,原始路径原样透传,
# 与 /api/ 那条 `proxy_pass .../api/` 的改写结果一致。
proxy_pass $app_backend;
include /etc/nginx/api-proxy.conf;
}
# API请求代理到后端服务
# APP_SCHEME 默认 http,远程 HTTPS 后端可设为 https
location /api/ {
proxy_pass $app_backend;
include /etc/nginx/api-proxy.conf;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}