diff --git a/docs/en/deployment.md b/docs/en/deployment.md index ebbfca8..bf79c8e 100644 --- a/docs/en/deployment.md +++ b/docs/en/deployment.md @@ -13,19 +13,20 @@ The frontend is built in GitHub Actions and shipped as a prebuilt `linux/amd64` image. The host only pulls the image and starts containers; it does not build locally. -`yarn prebuild` bundles the docs, blog, and static content needed by the console. During that phase the CI container runs `scripts/sync-doc-content.sh` (pulling docs from this repo plus `accounts.svc.plus`, `rag-server.svc.plus`, and `postgresql.svc.plus`) and `scripts/sync-blog-content.sh` (cloning `https://github.com/cloud-neutral-workshop/knowledge.git`), so the `knowledge/` directory and all documentation assets already live inside the image before the runtime stage begins. +`yarn prebuild` now generates only console-owned marketing artifacts. `/docs` and `/blogs` no longer bundle `knowledge/` or synced markdown content into the frontend image. Those routes fetch rendered content from `docs.svc.plus` at request time through the server-side `docsServiceClient`. The stack is static-first: - Caddy serves `/_next/static/*` and public assets from a shared read-only volume. - The Next.js standalone container serves dynamic HTML, auth endpoints, and API proxy routes. Static assets and hashed CSS/JS files are extracted by the `frontend-assets` helper task, so the runtime no longer needs to compile anything on the single-node host. -- `knowledge/` and the synced docs/blog assets are copied into the image during the Docker build via the GitHub Actions workflow. +- `docs.svc.plus` is the source of truth for rendered docs/blog pages; the browser does not call it directly. Releases are orchestrated through `.github/workflows/service_release_frontend-deploy.yml`. That workflow clones the knowledge repository, runs the Docker build/push sequence, renders `.env.runtime`, and ships `docker-compose.yml`, `Caddyfile`, and the runtime env file to the host. The control-plane workflow `.github/workflows/service_release_apiserver-deploy.yml` then updates Cloudflare DNS for the release domain (via `scripts/github-actions/update-release-dns.sh`) so `cn.svc.plus` and the redirected alias `cn.onwalk.net` point at the new environment. +Releases are orchestrated through `.github/workflows/service_release_frontend-deploy.yml`. That workflow builds/pushes the image, renders `.env.runtime` including `DOCS_SERVICE_URL` / `DOCS_SERVICE_INTERNAL_URL`, and ships `docker-compose.yml`, `Caddyfile`, and the runtime env file to the host. The control-plane workflow `.github/workflows/service_release_apiserver-deploy.yml` then updates Cloudflare DNS for the release domain (via `scripts/github-actions/update-release-dns.sh`) so `cn.svc.plus` and the redirected alias `cn.onwalk.net` point at the new environment. This baseline is intentional for the weak-IO single-node host (47.120.61.35). No images are built on the target machine, keeping the deployment lightweight: the host only logs into GHCR, pulls the `dashboard` image, extracts assets into `frontend_static`, and starts `dashboard` plus `caddy` containers via `docker compose`. -If `docs.svc.plus` is later refactored into a dedicated API service, revisit this writeup (and the runbook) so the GitHub Actions pipeline only bundles the API payloads that belong to that new service. +`docs.svc.plus` is now the dedicated docs/blog service for the frontend delivery path. ## Related Docs diff --git a/docs/usage/deployment.md b/docs/usage/deployment.md index 7c5e129..9a8a607 100644 --- a/docs/usage/deployment.md +++ b/docs/usage/deployment.md @@ -17,19 +17,15 @@ The production frontend is deployed as a prebuilt container image from GitHub Ac - The target host does not build images locally. - The workflow builds an `linux/amd64` image and pushes it to `ghcr.io//dashboard:`. - The host only performs `docker login`, `docker compose pull`, static asset extraction, and `docker compose up`. -- `knowledge/` is cloned during CI build (via `scripts/sync-blog-content.sh`) and synced with other docs (via `scripts/sync-doc-content.sh`) before being packed into the image. +- `/docs` and `/blogs` fetch their content from `docs.svc.plus` at runtime; the frontend image no longer packs `knowledge/` or synced markdown payloads. - Static assets are extracted from the image into a shared Docker volume so Caddy can serve `/_next/static/*` and checked-in public files directly. -This is intentionally static-first for the current weak-IO single-node host. Dynamic HTML, auth routes, and API proxy routes still run through the Next.js container. When `docs.svc.plus` is later split into an API/service, revisit this runbook and remove docs content from the frontend image. +This is intentionally static-first for the current weak-IO single-node host. Dynamic HTML, auth routes, and API proxy routes still run through the Next.js container, but docs/blog content delivery is now delegated to `docs.svc.plus`. ## Control Plane & DNS Stage The control repo (`github-org-x-evor`) tracks `console.svc.plus` through `console.svc.plus.code-workspace` and keeps the `subrepos/accounts.svc.plus` pointer in sync via `skills/cross-repo-upstream-submodule-sync`. Releases resolve metadata with that workspace and the `config/single-node-release` manifests. After `.github/workflows/service_release_frontend-deploy.yml` finishes pushing the new image, the control-plane workflow `.github/workflows/service_release_apiserver-deploy.yml` calls `scripts/github-actions/update-release-dns.sh` to update Cloudflare DNS so the new endpoint is reachable under `cn.svc.plus` and `cn.onwalk.net`. -## Future Docs Strategy - -Because the frontend currently ships docs content directly (knowledge/blog + rendered markdown), any future split where `docs.svc.plus` becomes an API-backed service should include a repo-level migration plan: stop syncing docs into the frontend image, move documentation storage/serving into the dedicated API, and adjust the runbook/workflow notes above accordingly. - ## Runtime Layout Remote directory: @@ -83,6 +79,8 @@ Repository/environment variables recommended: - `NEXT_PUBLIC_SERVER_SERVICE_URL` - `RUNTIME_HOSTNAME` - `DEPLOYMENT_HOSTNAME` +- `DOCS_SERVICE_URL` +- `DOCS_SERVICE_INTERNAL_URL` - `NEXT_PUBLIC_RUNTIME_ENVIRONMENT` - `NEXT_PUBLIC_RUNTIME_REGION` - `NEXT_PUBLIC_GISCUS_*` @@ -94,14 +92,13 @@ Repository/environment variables recommended: ## Release Flow 1. GitHub Actions checks out the repo. -2. GitHub Actions clones `knowledge/`. -3. Docker builds the frontend image with the public `NEXT_PUBLIC_*` values needed at build time. -4. The image is pushed to GHCR. -5. The workflow runs a matrix DNS stage, updating one public domain per job. -6. The workflow renders `.env.runtime`. -7. The workflow uploads `docker-compose.yml`, `Caddyfile`, and `.env.runtime` to the host. -8. The host pulls the new image, refreshes the static asset volume, and starts `dashboard + caddy`. -9. The workflow verifies `cn.svc.plus` and `cn.onwalk.net`. +2. Docker builds the frontend image with the public `NEXT_PUBLIC_*` values needed at build time. +3. The image is pushed to GHCR. +4. The workflow runs a matrix DNS stage, updating one public domain per job. +5. The workflow renders `.env.runtime`, including docs service runtime endpoints. +6. The workflow uploads `docker-compose.yml`, `Caddyfile`, and `.env.runtime` to the host. +7. The host pulls the new image, refreshes the static asset volume, and starts `dashboard + caddy`. +8. The workflow verifies `cn.svc.plus` and `cn.onwalk.net`. ## Verification Commands diff --git a/docs/zh/deployment.md b/docs/zh/deployment.md index 990e9c6..fa09381 100644 --- a/docs/zh/deployment.md +++ b/docs/zh/deployment.md @@ -13,19 +13,19 @@ 前端镜像在 GitHub Actions 中完成构建并推送到镜像仓库,目标主机只负责拉取镜像和启动容器,不在机器上本地构建。 -`yarn prebuild` 会同步 docs、博客和其它静态内容。CI 在该阶段执行 `scripts/sync-doc-content.sh`(从 `console.svc.plus`、`accounts.svc.plus`、`rag-server.svc.plus` 和 `postgresql.svc.plus` 拉取文档)以及 `scripts/sync-blog-content.sh`(克隆 `https://github.com/cloud-neutral-workshop/knowledge.git`),因此 `knowledge/` 目录和所有文档/博客资产在构建镜像时就已存在。 +`yarn prebuild` 现在只生成 console 自己的营销内容工件。`/docs` 与 `/blogs` 不再把 `knowledge/` 或 Markdown 文档打进前端镜像,而是在请求时通过服务端 `docsServiceClient` 从 `docs.svc.plus` 拉取渲染后的内容。 当前方案尽量以静态模式运行: - Caddy 直接服务 `/_next/static/*` 与 `public/` 里的静态资源,并配合 `frontend_static` 共享卷。 - Next.js standalone 容器只承接动态页面、认证接口和代理接口,`frontend-assets` 任务会把所有静态文件(包括哈希后的 CSS/JS)拷贝到 `frontend_static`。 -- `knowledge/` 与同步的文档/博客内容在 GitHub Actions 的 Docker 构建阶段就被写入镜像。 +- `docs.svc.plus` 是 docs/blog 的运行时内容源,浏览器不会直接访问它。 -发布由 `.github/workflows/service_release_frontend-deploy.yml` 驱动,CI 构建/推送镜像、渲染 `.env.runtime`,然后将 `docker-compose.yml`、`Caddyfile` 与运行时环境文件发送到主机。随后控制平面工作流 `.github/workflows/service_release_apiserver-deploy.yml` 通过 `scripts/github-actions/update-release-dns.sh` 更新 Cloudflare DNS,使 `cn.svc.plus` 与别名 `cn.onwalk.net` 指向更新后的环境。 +发布由 `.github/workflows/service_release_frontend-deploy.yml` 驱动,CI 构建/推送镜像、渲染包含 `DOCS_SERVICE_URL` / `DOCS_SERVICE_INTERNAL_URL` 的 `.env.runtime`,然后将 `docker-compose.yml`、`Caddyfile` 与运行时环境文件发送到主机。随后控制平面工作流 `.github/workflows/service_release_apiserver-deploy.yml` 通过 `scripts/github-actions/update-release-dns.sh` 更新 Cloudflare DNS,使 `cn.svc.plus` 与别名 `cn.onwalk.net` 指向更新后的环境。 这是针对弱 IO 单机主机 `47.120.61.35` 的部署权衡:主机不会在本地构建镜像,只需登录 GHCR、拉取 `dashboard` 镜像、解包静态资源到 `frontend_static`,再通过 `docker compose` 启动 `dashboard` 与 `caddy`。 -未来如果 `docs.svc.plus` 被拆分成独立的 API 服务,必须同步更新这份说明(以及运行手册),让 GitHub Actions 只打包属于新服务的内容。 +`docs.svc.plus` 已经是前端 docs/blog 内容的独立服务。 ## 相关文档 diff --git a/scripts/github-actions/prepare-frontend-build-context.sh b/scripts/github-actions/prepare-frontend-build-context.sh index 12a95c6..d3de7eb 100755 --- a/scripts/github-actions/prepare-frontend-build-context.sh +++ b/scripts/github-actions/prepare-frontend-build-context.sh @@ -1,10 +1,4 @@ #!/usr/bin/env bash set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -KNOWLEDGE_DIR="${REPO_ROOT}/knowledge" -KNOWLEDGE_REPO="${KNOWLEDGE_REPO:-https://github.com/Cloud-Neutral-Workshop/knowledge.git}" - -rm -rf "${KNOWLEDGE_DIR}" -git clone --depth=1 "${KNOWLEDGE_REPO}" "${KNOWLEDGE_DIR}" +echo "frontend build context no longer syncs docs/blog content; using docs.svc.plus at runtime." diff --git a/scripts/github-actions/render-frontend-runtime-env.sh b/scripts/github-actions/render-frontend-runtime-env.sh index e118c3b..c4403e0 100755 --- a/scripts/github-actions/render-frontend-runtime-env.sh +++ b/scripts/github-actions/render-frontend-runtime-env.sh @@ -50,6 +50,8 @@ append_env NEXT_PUBLIC_ACCOUNT_SERVICE_URL "${NEXT_PUBLIC_ACCOUNT_SERVICE_URL:-$ append_env SERVER_SERVICE_URL "${SERVER_SERVICE_URL:-https://api.svc.plus}" append_env NEXT_PUBLIC_SERVER_SERVICE_URL "${NEXT_PUBLIC_SERVER_SERVICE_URL:-${SERVER_SERVICE_URL:-https://api.svc.plus}}" append_env SERVER_SERVICE_INTERNAL_URL "${SERVER_SERVICE_INTERNAL_URL-}" +append_env DOCS_SERVICE_URL "${DOCS_SERVICE_URL:-https://docs.svc.plus}" +append_env DOCS_SERVICE_INTERNAL_URL "${DOCS_SERVICE_INTERNAL_URL-}" append_env OPENCLAW_GATEWAY_REMOTE_URL "${OPENCLAW_GATEWAY_REMOTE_URL-}" append_env OPENCLAW_GATEWAY_TOKEN "${OPENCLAW_GATEWAY_TOKEN-}" append_env VAULT_SERVER_URL "${VAULT_SERVER_URL-}" diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index e38dae7..5992ad8 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -11,12 +11,12 @@ echo "======================================" echo "Starting prebuild process..." echo "======================================" -# Step 1: Sync documentation from service repositories +# Step 1: Generate local marketing content artifacts echo "" -echo "[1/2] Generating static content..." +echo "[1/2] Generating marketing content..." npx tsx scripts/generate-content.ts -# Step 2: Build contentlayer +# Step 2: Build contentlayer artifacts used by non-doc pages echo "" echo "[2/2] Building contentlayer..." node scripts/build-contentlayer.mjs diff --git a/src/app/blogs/page.tsx b/src/app/blogs/page.tsx index f5da971..0da0f0f 100644 --- a/src/app/blogs/page.tsx +++ b/src/app/blogs/page.tsx @@ -6,7 +6,6 @@ import { Suspense } from "react"; import BlogList from "@components/blog/BlogList"; import { PublicPageShell } from "@/components/public/PublicPageShell"; -import type { BlogCategoryPayload, BlogPostPayload } from "@lib/docsServiceClient"; import { getBlogList } from "@lib/docsServiceClient"; export const metadata: Metadata = { @@ -17,9 +16,15 @@ export const metadata: Metadata = { export default async function BlogPage() { const listing = await getBlogList({ page: 1, pageSize: 200 }); - const categories: BlogCategoryPayload[] = listing.categories; + const categories = listing.categories; const postsWithoutContent = listing.posts.map( - ({ html: _html, plaintext: _plaintext, sourcePath: _sourcePath, language: _language, ...post }: BlogPostPayload) => post, + ({ + html: _html, + plaintext: _plaintext, + sourcePath: _sourcePath, + language: _language, + ...post + }) => post, ); return ( diff --git a/src/components/blog/BlogList.tsx b/src/components/blog/BlogList.tsx index 01ceb64..a2793b6 100644 --- a/src/components/blog/BlogList.tsx +++ b/src/components/blog/BlogList.tsx @@ -9,7 +9,21 @@ import BrandCTA from "@components/BrandCTA"; import { PublicPageIntro } from "@/components/public/PublicPageShell"; import SearchComponent from "@components/search"; import { useLanguage } from "@i18n/LanguageProvider"; -import type { BlogCategory, BlogPostSummary } from "@lib/blogContent"; + +type BlogCategory = { + key: string; + label: string; +}; + +type BlogPostSummary = { + slug: string; + title: string; + author?: string; + date?: string; + tags: string[]; + excerpt: string; + category?: BlogCategory; +}; function formatDate( dateStr: string | undefined,