Linux 部署指南

dotnet publish → tar.gz → systemd + Nginx 完整链路;无 CI 也能一键上线。

最后更新

Known 侧所有服务端进程(营销站 Website / 后台全栈宿主 KnownWeb)都按同一份 Linux 部署约定: dotnet publish 目录产物 + systemd 单元 + Nginx 反代。Website 已交付这套脚本, KnownWeb 沿用同一模式。

一键流程

# 开发机(Windows / PowerShell)
cd Website
pwsh deploy/build.ps1                 # 出 deploy/artifacts/known-website-Release-linux-x64-2026...tar.gz
scp deploy/artifacts/*.tar.gz user@host:/tmp/
# 服务器(Linux)
/tmp/install.sh /tmp/known-website-Release-linux-x624-xxx.tar.gz

install.sh 内部做的事:解包到 /var/www/known-website.new → 冒烟起临时进程 curl 200 → 原子 mv 切换 → systemctl restart known-website → 线上再打一次 /robots.txt 探活。 失败保留 .old 供回滚。

systemd 单元要点

deploy/systemd/known-website.service:

  • Type=notify — .NET 10 内建 systemd readiness 通知,比 simple 更准确;
  • User=knownwww — 非 root;
  • Environment=ASPNETCORE_URLS=http://127.0.0.1:5000 — 只监听 loopback,不暴露公网;
  • Environment=ASPNETCORE_FORWARDEDHEADERS_ENABLED=1 — 读 Nginx 传上来的 X-Forwarded-*;
  • KillSignal=SIGINT TimeoutStopSec=25 — 优雅关闭、给应用 25s 收尾;
  • 硬化:ProtectSystem=strict PrivateTmp NoNewPrivileges RestrictNamespaces LockPersonality RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6;
  • 例外:MemoryDenyWriteExecute=false(.NET JIT 需要 W^X 例外,别照抄网上模板);
  • ReadWritePaths=/var/log/known-website — 只有这一个可写目录,其余全 ReadOnly。

Nginx 反代要点

deploy/nginx/known-website.conf:

  • HTTP 段:server_name www.known.org.cn; return 301 https://$host$request_uri; 例外放行 /.well-known/acme-challenge/ 到 webroot 供 certbot;
  • HTTPS 段:listen 443 ssl http2; TLS 1.2/1.3、HSTS max-age=31536000; includeSubDomains;
  • 三段缓存策略:
    • 指纹静态资源(site.<hash>.css / /_framework/*):immutable, max-age=31536000;
    • SEO 端点(/robots.txt /sitemap.xml /llms.txt):max-age=3600;
    • HTML:no-cache。
  • 透传上游:proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host;
  • gzip 6、CSP 保守(default-src 'self' + Google Fonts + 内联 pre-paint 脚本 unsafe-inline)。

Docker 备选

营销站根下 Dockerfile 是多阶段:sdk:10.0 构建 → aspnet:10.0 运行,非 root $APP_UID=10001、 HEALTHCHECK curl /。deploy/docker-compose.yml 加了 read_only + cap_drop ALL + no-new-privileges + tmpfs /tmp。

海外发布(Blazor SSR 不能扔 Cloudflare Pages 那种纯静态托管)建议: Fly.io / Workers for Containers / 便宜 VPS + 这份 Dockerfile。

环境变量与 profile

营销站的子域配置走 Site__* 环境变量(详见 Website/docs/deploy-topology.md §5):

Site__Canonical=https://www.known.org.cn
Site__App=https://app.known.org.cn
Site__Api=https://api.known.org.cn
Site__Play=https://play.known.org.cn
Site__Gitee=https://gitee.com/known/Known

绑定优先级:命令行 --Site:Canonical > 环境变量 Site__Canonical > appsettings.Production.json

appsettings.json。切 profile 不换产物、只换 env。

Site__Docs Site__Status 已从配置移除——docs 与 status 走营销站内子树,不再是独立子域。

冒烟清单

上线前后各跑一次:

BASE=https://www.known.org.cn
for p in / /features /pricing /templates /docs /docs/getting-started /docs/concepts \
         /docs/guides /docs/faq /docs/changelog /roadmap /status \
         /about /contact /legal /legal/terms /legal/privacy /legal/license /legal/sla \
         /robots.txt /sitemap.xml /llms.txt /not-found; do
  printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' "$BASE$p")"; echo "$p"
done
# 期望:前 19 页 200;SEO 三端点 200;/not-found 也 200(Router.NotFoundPage 命中)
# 另测 /nope-random 应 404(UseStatusCodePagesWithReExecute 保持 404 状态码)

备案与合规

  • 国内直投 .org.cn 域名需 ICP 备案;页脚已挂 苏ICP备20045379号-3 · 公安备案。
  • 海外 profile 若走非 .org.cn 域,需要在 Footer.razor 判 Site.Canonical 是否含 .org.cn 决定是否渲染备案链接(当前留白,未实装)。
  • Cookie 同意条:本站只签发严格必要 Cookie(会话 / CSRF 防护),无分析或追踪类 Cookie,多数法域豁免同意,不需要 GDPR/Cookie 弹窗。

常见坑

  • MemoryDenyWriteExecute=true — 抄网上模板把这个开了,.NET JIT 立即崩、进程起不来。Known 的 systemd 单元已明确留例外。
  • Nginx 忘了 proxy_http_version 1.1 — 上游连接每次重建、性能差;单元里默认已配。
  • install.sh 冒烟打的是 5099 而不是 5000 — 冒烟阶段临时端口,与 systemd 生产端口不冲突;改脚本时别把两处混一起。
  • /var/log/known-website 属主不对 — journalctl 之外若要落文件日志,ReadWritePaths 里的目录 chown knownwww:knownwww。

相关

  • 完整部署包与文档:Website/deploy/README.md、Website/docs/deploy-topology.md。
  • 后台全栈宿主 KnownWeb(同进程 serve WASM 客户端 + [WebApi] 端点 + Cookie 鉴权)部署与上面同构,套用同一份 systemd + Nginx 约定即可。
微信公众号二维码 扫码关注公众号
QQ 交流群二维码 扫码加入 QQ 群