Align frontend release contract across www and console domains
This commit is contained in:
parent
22e95e5bcb
commit
d054b35116
9
.github/workflows/pipeline.yaml
vendored
9
.github/workflows/pipeline.yaml
vendored
@ -7,12 +7,15 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/pipeline.yaml"
|
||||
- "Dockerfile"
|
||||
- "deploy/single-node/**"
|
||||
- "package.json"
|
||||
- "yarn.lock"
|
||||
- "scripts/github-actions/build-and-push-frontend-image.sh"
|
||||
- "scripts/github-actions/compute-frontend-release-metadata.sh"
|
||||
- "scripts/github-actions/render-frontend-build-args.sh"
|
||||
- "scripts/github-actions/render-frontend-runtime-env.sh"
|
||||
- "scripts/github-actions/prepare-frontend-build-context.sh"
|
||||
- "scripts/github-actions/run-console-deploy-playbook.sh"
|
||||
- "scripts/github-actions/verify-frontend-release.sh"
|
||||
- "scripts/prebuild.sh"
|
||||
- "contentlayer.config.ts"
|
||||
@ -42,7 +45,8 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PRIMARY_DOMAIN: console.svc.plus
|
||||
CANONICAL_DOMAIN: www.svc.plus
|
||||
SERVED_DOMAINS: www.svc.plus,console.svc.plus
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT: prod
|
||||
NEXT_PUBLIC_RUNTIME_REGION: cn
|
||||
ACCOUNT_SERVICE_URL: https://accounts.svc.plus
|
||||
@ -176,4 +180,5 @@ jobs:
|
||||
- name: Verify Frontend Release
|
||||
run: |
|
||||
bash scripts/github-actions/verify-frontend-release.sh \
|
||||
"${PRIMARY_DOMAIN}"
|
||||
"${CANONICAL_DOMAIN}" \
|
||||
"${SERVED_DOMAINS}"
|
||||
|
||||
@ -1,21 +1,22 @@
|
||||
# Compose settings
|
||||
FRONTEND_IMAGE=ghcr.io/cloud-neutral-toolkit/dashboard:replace-me
|
||||
PRIMARY_DOMAIN=cn-console.svc.plus
|
||||
CANONICAL_DOMAIN=www.svc.plus
|
||||
SERVED_DOMAINS=www.svc.plus,console.svc.plus
|
||||
|
||||
# Frontend runtime
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
RUNTIME_ENV=prod
|
||||
REGION=cn
|
||||
APP_BASE_URL=https://cn-console.svc.plus
|
||||
NEXT_PUBLIC_APP_BASE_URL=https://cn-console.svc.plus
|
||||
NEXT_PUBLIC_SITE_URL=https://cn-console.svc.plus
|
||||
NEXT_PUBLIC_LOGIN_URL=https://cn-console.svc.plus/login
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=https://cn-console.svc.plus/docs
|
||||
APP_BASE_URL=https://www.svc.plus
|
||||
NEXT_PUBLIC_APP_BASE_URL=https://www.svc.plus
|
||||
NEXT_PUBLIC_SITE_URL=https://www.svc.plus
|
||||
NEXT_PUBLIC_LOGIN_URL=https://www.svc.plus/login
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=https://www.svc.plus/docs
|
||||
SESSION_COOKIE_SECURE=true
|
||||
NEXT_PUBLIC_SESSION_COOKIE_SECURE=true
|
||||
RUNTIME_HOSTNAME=cn-console.svc.plus
|
||||
DEPLOYMENT_HOSTNAME=cn-console.svc.plus
|
||||
RUNTIME_HOSTNAME=www.svc.plus
|
||||
DEPLOYMENT_HOSTNAME=www.svc.plus
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=prod
|
||||
NEXT_PUBLIC_RUNTIME_REGION=cn
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{$PRIMARY_DOMAIN} {
|
||||
{$SERVED_DOMAINS} {
|
||||
encode zstd gzip
|
||||
|
||||
handle_path /_next/static/* {
|
||||
|
||||
@ -36,7 +36,7 @@ services:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
PRIMARY_DOMAIN: ${PRIMARY_DOMAIN:?set PRIMARY_DOMAIN in .env.runtime}
|
||||
SERVED_DOMAINS: ${SERVED_DOMAINS:?set SERVED_DOMAINS in .env.runtime}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- frontend_static:/srv:ro
|
||||
|
||||
@ -214,7 +214,7 @@ export default function NotFound() {
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://console.svc.plus'),
|
||||
metadataBase: new URL('https://www.svc.plus'),
|
||||
title: {
|
||||
default: 'Cloud-Neutral | Unified Cloud Native Tools',
|
||||
template: '%s | Cloud-Neutral',
|
||||
@ -232,7 +232,7 @@ export const metadata: Metadata = {
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
url: 'https://console.svc.plus',
|
||||
url: 'https://www.svc.plus',
|
||||
title: 'Cloud-Neutral | Unified Cloud Native Tools',
|
||||
description: 'Unified tools for your cloud native stack',
|
||||
siteName: 'Cloud-Neutral',
|
||||
@ -274,7 +274,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#6366f1" />
|
||||
<link rel="canonical" href="https://console.svc.plus" />
|
||||
<link rel="canonical" href="https://www.svc.plus" />
|
||||
{/* ... rest of head */}
|
||||
</head>
|
||||
{/* ... rest of layout */}
|
||||
@ -343,7 +343,7 @@ Disallow: /admin/
|
||||
Disallow: /api/
|
||||
Disallow: /internal/
|
||||
|
||||
Sitemap: https://console.svc.plus/sitemap.xml
|
||||
Sitemap: https://www.svc.plus/sitemap.xml
|
||||
```
|
||||
|
||||
---
|
||||
@ -360,8 +360,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: 'Cloud-Neutral',
|
||||
url: 'https://console.svc.plus',
|
||||
logo: 'https://console.svc.plus/logo.png',
|
||||
url: 'https://www.svc.plus',
|
||||
logo: 'https://www.svc.plus/logo.png',
|
||||
sameAs: [
|
||||
'https://twitter.com/cloudneutral',
|
||||
'https://github.com/x-evor',
|
||||
@ -372,10 +372,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'Cloud-Neutral',
|
||||
url: 'https://console.svc.plus',
|
||||
url: 'https://www.svc.plus',
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: 'https://console.svc.plus/search?q={search_term_string}',
|
||||
target: 'https://www.svc.plus/search?q={search_term_string}',
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
}
|
||||
|
||||
@ -4,9 +4,11 @@
|
||||
|
||||
- Runtime: `Caddy + Docker Compose`
|
||||
- Deploy host: `root@cn-console.svc.plus`
|
||||
- Domains:
|
||||
- `cn-console.svc.plus`
|
||||
- Frontend release workflow: `.github/workflows/service_release_frontend-deploy.yml`
|
||||
- Public domains:
|
||||
- `www.svc.plus`
|
||||
- `console.svc.plus`
|
||||
- Canonical public origin: `https://www.svc.plus`
|
||||
- Frontend release workflow: `.github/workflows/pipeline.yaml`
|
||||
|
||||
## Operating Model
|
||||
|
||||
@ -20,7 +22,14 @@ The stack is static-first:
|
||||
- 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.
|
||||
- `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 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-console.svc.plus` points at the new environment.
|
||||
Releases are orchestrated through `.github/workflows/pipeline.yaml`. 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 DNS automation then updates Cloudflare DNS for the release domains (via `scripts/github-actions/update-release-dns.sh`) so both `www.svc.plus` and `console.svc.plus` resolve to the same environment.
|
||||
|
||||
The release contract now uses:
|
||||
|
||||
- `CANONICAL_DOMAIN=www.svc.plus`
|
||||
- `SERVED_DOMAINS=www.svc.plus,console.svc.plus`
|
||||
|
||||
Validation must pass for both domains. A release is incomplete if either host serves a different runtime version, static asset family, or `dashboardUrl`.
|
||||
|
||||
This baseline is intentional for the weak-IO single-node host `root@cn-console.svc.plus`. 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`.
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Release Process
|
||||
|
||||
This page tracks release summaries for published versions of `console.svc.plus`.
|
||||
This page tracks release summaries for published versions of the public web console served under `www.svc.plus` and `console.svc.plus`.
|
||||
|
||||
## Current Release
|
||||
|
||||
@ -49,3 +49,5 @@ Published commit: `0fab89e`
|
||||
|
||||
- GitHub Release: `https://github.com/x-evor/console.svc.plus/releases/tag/v0.2`
|
||||
- Related docs: `docs/README.md`, `docs/en/README.md`, `docs/zh/README.md`
|
||||
- Release validation must verify both `www.svc.plus` and `console.svc.plus` against the same `releaseImageRef`, `releaseImageTag`, and `releaseCommit`.
|
||||
- `www.svc.plus` is the canonical public domain for metadata, sitemap, `dashboardUrl`, and shared links.
|
||||
|
||||
@ -48,7 +48,7 @@ CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
|
||||
|
||||
### 本地开发
|
||||
|
||||
写入 `console.svc.plus/.env.local`:
|
||||
写入当前前端仓库的 `.env.local`:
|
||||
|
||||
```bash
|
||||
CLOUDFLARE_API_TOKEN=...
|
||||
@ -58,7 +58,7 @@ CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=...
|
||||
|
||||
### 线上部署
|
||||
|
||||
把同名变量写入 `console.svc.plus` 的部署环境(例如 Vercel/Cloud Run 的环境变量配置)。
|
||||
把同名变量写入前端部署环境。
|
||||
|
||||
> 注意:这些变量属于服务端密钥,不要暴露到 `NEXT_PUBLIC_*`。
|
||||
|
||||
@ -67,7 +67,7 @@ CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=...
|
||||
部署后访问:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://console.svc.plus/api/marketing/home-stats
|
||||
curl -fsSL https://www.svc.plus/api/marketing/home-stats
|
||||
```
|
||||
|
||||
期望返回中 `visits.daily/weekly/monthly` 为数字(非 `null`)。
|
||||
@ -77,4 +77,3 @@ curl -fsSL https://console.svc.plus/api/marketing/home-stats
|
||||
1. token 权限是否包含 Analytics Read
|
||||
2. Account ID 是否与 siteTag 属于同一账号
|
||||
3. 环境变量是否已在当前运行实例生效(重启/重新部署后再测)
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ This guide describes how to configure GitHub and Google OAuth login for the Clou
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||
│ Browser │ │ console.svc.plus │ │accounts.svc.plus │
|
||||
│ Browser │ │ www.svc.plus │ │accounts.svc.plus │
|
||||
│ (User) │ │ (Frontend) │ │ (Backend) │
|
||||
└──────┬───────┘ └────────┬─────────┘ └────────┬─────────┘
|
||||
│ 1. Click "Login │ │
|
||||
@ -40,7 +40,7 @@ This guide describes how to configure GitHub and Google OAuth login for the Clou
|
||||
|
||||
- A GitHub account with access to **Settings > Developer Settings**
|
||||
- A Google account with access to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
- Running `accounts.svc.plus` and `console.svc.plus` services
|
||||
- Running `accounts.svc.plus` and the frontend served under `www.svc.plus` / `console.svc.plus`
|
||||
|
||||
---
|
||||
|
||||
@ -55,7 +55,7 @@ This guide describes how to configure GitHub and Google OAuth login for the Clou
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Application name** | `Cloud Neutral Console` |
|
||||
| **Homepage URL** | `https://console.svc.plus` |
|
||||
| **Homepage URL** | `https://www.svc.plus` |
|
||||
| **Authorization callback URL** | `https://accounts.svc.plus/api/auth/oauth/callback/github` |
|
||||
| **Enable Device Flow** | ☐ (unchecked) |
|
||||
|
||||
@ -119,7 +119,7 @@ No additional GitHub permissions are required.
|
||||
|---|---|
|
||||
| **Application type** | `Web application` |
|
||||
| **Name** | `Cloud Neutral Console` |
|
||||
| **Authorized JavaScript origins** | `https://console.svc.plus` |
|
||||
| **Authorized JavaScript origins** | `https://www.svc.plus` |
|
||||
| **Authorized redirect URIs** | `https://accounts.svc.plus/api/auth/oauth/callback/google` |
|
||||
|
||||
4. Click **"Create"**
|
||||
@ -150,7 +150,7 @@ GOOGLE_CLIENT_SECRET=<your_google_client_secret>
|
||||
|
||||
# ── General OAuth ──
|
||||
OAUTH_REDIRECT_URL=https://accounts.svc.plus/api/auth/oauth/callback
|
||||
OAUTH_FRONTEND_URL=https://console.svc.plus
|
||||
OAUTH_FRONTEND_URL=https://www.svc.plus
|
||||
```
|
||||
|
||||
These variables are referenced in `config/account.yaml`:
|
||||
@ -159,7 +159,7 @@ These variables are referenced in `config/account.yaml`:
|
||||
auth:
|
||||
oauth:
|
||||
redirectUrl: "${OAUTH_REDIRECT_URL}"
|
||||
frontendUrl: "${OAUTH_FRONTEND_URL:-https://console.svc.plus}"
|
||||
frontendUrl: "${OAUTH_FRONTEND_URL:-https://www.svc.plus}"
|
||||
github:
|
||||
clientId: "${GITHUB_CLIENT_ID}"
|
||||
clientSecret: "${GITHUB_CLIENT_SECRET}"
|
||||
@ -172,7 +172,7 @@ auth:
|
||||
|
||||
---
|
||||
|
||||
## 4. Frontend Configuration (console.svc.plus)
|
||||
## 4. Frontend Configuration (`www.svc.plus` canonical, `console.svc.plus` secondary)
|
||||
|
||||
The frontend resolves the accounts service URL **server-side** via `getAccountServiceBaseUrl()`, which reads:
|
||||
|
||||
@ -209,7 +209,7 @@ If not set, the function falls back to a runtime default. **No `NEXT_PUBLIC_*` e
|
||||
|
||||
### OAuth login redirects to wrong domain
|
||||
|
||||
Check that `OAUTH_FRONTEND_URL` in accounts.svc.plus matches the console domain where users should be redirected after authentication.
|
||||
Check that `OAUTH_FRONTEND_URL` in accounts.svc.plus matches the canonical public domain where users should be redirected after authentication. The current default is `https://www.svc.plus`.
|
||||
|
||||
### Google "Access blocked: This app's request is invalid"
|
||||
|
||||
|
||||
@ -6,8 +6,9 @@
|
||||
- Topology: `Caddy + Docker Compose + GitHub Actions`
|
||||
- Deploy host: `root@cn-console.svc.plus`
|
||||
- Public domains:
|
||||
- `https://cn-console.svc.plus`
|
||||
- Primary origin: `https://cn-console.svc.plus`
|
||||
- `https://www.svc.plus`
|
||||
- `https://console.svc.plus`
|
||||
- Canonical public origin: `https://www.svc.plus`
|
||||
|
||||
## Current Delivery Model
|
||||
|
||||
@ -23,7 +24,7 @@ This is intentionally static-first for the current weak-IO single-node host. Dyn
|
||||
|
||||
## 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-console.svc.plus`.
|
||||
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/pipeline.yaml` finishes pushing the new image, the control-plane DNS automation calls `scripts/github-actions/update-release-dns.sh` to update Cloudflare DNS so the new endpoint is reachable under `cn-console.svc.plus`.
|
||||
|
||||
## Runtime Layout
|
||||
|
||||
@ -52,7 +53,7 @@ Containers:
|
||||
Workflow:
|
||||
|
||||
```text
|
||||
.github/workflows/service_release_frontend-deploy.yml
|
||||
.github/workflows/pipeline.yaml
|
||||
```
|
||||
|
||||
Secrets required:
|
||||
@ -67,6 +68,8 @@ Secrets required:
|
||||
|
||||
Repository/environment variables recommended:
|
||||
|
||||
- `CANONICAL_DOMAIN`
|
||||
- `SERVED_DOMAINS`
|
||||
- `APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_SITE_URL`
|
||||
@ -94,10 +97,10 @@ Repository/environment variables recommended:
|
||||
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 updates Cloudflare DNS for the release domain.
|
||||
5. The workflow renders `.env.runtime`, including docs service runtime endpoints and the `cn-console` origin settings.
|
||||
5. The workflow renders `.env.runtime`, including the canonical public origin and both served frontend domains.
|
||||
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-console.svc.plus`.
|
||||
8. The workflow verifies both `www.svc.plus` and `console.svc.plus`, and fails the release if either domain serves a different runtime version.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
@ -113,7 +116,7 @@ rm -f deploy/single-node/.env.runtime
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
yaml.safe_load(Path('.github/workflows/service_release_frontend-deploy.yml').read_text())
|
||||
yaml.safe_load(Path('.github/workflows/pipeline.yaml').read_text())
|
||||
print('workflow yaml ok')
|
||||
PY
|
||||
```
|
||||
@ -122,8 +125,10 @@ Remote checks:
|
||||
|
||||
```bash
|
||||
ssh root@cn-console.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime ps"
|
||||
ssh root@cn-console.svc.plus "curl -fsSI -H 'Host: cn-console.svc.plus' http://127.0.0.1/"
|
||||
curl -fsSIL https://cn-console.svc.plus
|
||||
ssh root@cn-console.svc.plus "curl -fsSI -H 'Host: www.svc.plus' http://127.0.0.1/"
|
||||
ssh root@cn-console.svc.plus "curl -fsSI -H 'Host: console.svc.plus' http://127.0.0.1/"
|
||||
curl -fsSIL https://www.svc.plus
|
||||
curl -fsSIL https://console.svc.plus
|
||||
```
|
||||
|
||||
## Failure Signatures
|
||||
@ -132,7 +137,7 @@ curl -fsSIL https://cn-console.svc.plus
|
||||
The workflow token or package visibility is wrong.
|
||||
- `frontend-assets` fails
|
||||
The image layout changed and no longer contains `/app/dashboard/static` or `/app/dashboard/public`.
|
||||
- `cn-console.svc.plus` returns `502`
|
||||
- `www.svc.plus` or `console.svc.plus` returns `502`
|
||||
Caddy is up, but the `dashboard` container failed or is not reachable on port `3000`.
|
||||
|
||||
## Rollback
|
||||
@ -146,4 +151,4 @@ ssh root@cn-console.svc.plus "cd /opt/console-svc-plus && docker compose --env-f
|
||||
ssh root@cn-console.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime up -d dashboard caddy"
|
||||
```
|
||||
|
||||
4. Verify `https://cn-console.svc.plus` again before closing the incident.
|
||||
4. Verify `https://www.svc.plus` and `https://console.svc.plus` again before closing the incident.
|
||||
|
||||
@ -5,8 +5,10 @@
|
||||
- 运行拓扑: `Caddy + Docker Compose`
|
||||
- 目标主机: `root@cn-console.svc.plus`
|
||||
- 域名:
|
||||
- `cn-console.svc.plus`
|
||||
- 前端独立发布流水线: `.github/workflows/service_release_frontend-deploy.yml`
|
||||
- `www.svc.plus`
|
||||
- `console.svc.plus`
|
||||
- 公开首选域名: `www.svc.plus`
|
||||
- 前端独立发布流水线: `.github/workflows/pipeline.yaml`
|
||||
|
||||
## 运行方式
|
||||
|
||||
@ -20,12 +22,19 @@
|
||||
- Next.js standalone 容器只承接动态页面、认证接口和代理接口,`frontend-assets` 任务会把所有静态文件(包括哈希后的 CSS/JS)拷贝到 `frontend_static`。
|
||||
- `docs.svc.plus` 是 docs/blog 的运行时内容源,浏览器不会直接访问它。
|
||||
|
||||
发布由 `.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-console.svc.plus` 指向更新后的环境。
|
||||
发布由 `.github/workflows/pipeline.yaml` 驱动,CI 构建/推送镜像、渲染包含 `DOCS_SERVICE_URL` / `DOCS_SERVICE_INTERNAL_URL` 的 `.env.runtime`,然后将 `docker-compose.yml`、`Caddyfile` 与运行时环境文件发送到主机。随后控制平面 DNS 自动化会通过 `scripts/github-actions/update-release-dns.sh` 更新 Cloudflare DNS,使 `cn-console.svc.plus` 指向更新后的环境。
|
||||
|
||||
这是针对弱 IO 单机主机 `root@cn-console.svc.plus` 的部署权衡:主机不会在本地构建镜像,只需登录 GHCR、拉取 `dashboard` 镜像、解包静态资源到 `frontend_static`,再通过 `docker compose` 启动 `dashboard` 与 `caddy`。
|
||||
|
||||
`docs.svc.plus` 已经是前端 docs/blog 内容的独立服务。
|
||||
|
||||
当前发布合同要求:
|
||||
|
||||
- `CANONICAL_DOMAIN=www.svc.plus`
|
||||
- `SERVED_DOMAINS=www.svc.plus,console.svc.plus`
|
||||
- 流水线必须同时校验两个域名的首页静态资源与 `/api/ping` 版本元数据完全一致
|
||||
- `dashboardUrl`、canonical、structured data 与 sitemap 默认统一输出 `https://www.svc.plus`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `usage/deployment.md`
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
> English: `../../governance/release-process.md`
|
||||
|
||||
本页用于记录 `console.svc.plus` 已发布版本的发布说明与变更摘要。
|
||||
本页用于记录公开控制台在 `www.svc.plus` 与 `console.svc.plus` 下发布版本的说明与变更摘要。
|
||||
|
||||
## 当前版本
|
||||
|
||||
@ -50,4 +50,6 @@
|
||||
## 备注
|
||||
|
||||
- GitHub Release:`https://github.com/x-evor/console.svc.plus/releases/tag/v0.2`
|
||||
- 发布校验必须同时验证 `www.svc.plus` 与 `console.svc.plus` 的 `releaseImageRef`、`releaseImageTag`、`releaseCommit` 完全一致。
|
||||
- `www.svc.plus` 是 metadata、sitemap、`dashboardUrl` 与公开分享链接的首选域名。
|
||||
- 相关文档:`docs/README.md`、`docs/en/README.md`、`docs/zh/README.md`
|
||||
|
||||
@ -14,7 +14,7 @@ require_env() {
|
||||
}
|
||||
|
||||
require_env IMAGE_REF
|
||||
require_env PRIMARY_DOMAIN
|
||||
require_env CANONICAL_DOMAIN
|
||||
|
||||
BUILD_ARGS_FILE="$(mktemp)"
|
||||
trap 'rm -f "${BUILD_ARGS_FILE}"' EXIT
|
||||
|
||||
@ -21,7 +21,8 @@ require_env SINGLE_NODE_VPS_SSH_PRIVATE_KEY
|
||||
require_env GHCR_USERNAME
|
||||
require_env GHCR_PASSWORD
|
||||
require_env FRONTEND_IMAGE
|
||||
require_env PRIMARY_DOMAIN
|
||||
require_env CANONICAL_DOMAIN
|
||||
require_env SERVED_DOMAINS
|
||||
|
||||
GHCR_REGISTRY="${GHCR_REGISTRY:-ghcr.io}"
|
||||
|
||||
|
||||
@ -13,16 +13,16 @@ require_env() {
|
||||
}
|
||||
|
||||
emit_lines() {
|
||||
require_env PRIMARY_DOMAIN
|
||||
require_env CANONICAL_DOMAIN
|
||||
|
||||
local primary_domain="${PRIMARY_DOMAIN}"
|
||||
local canonical_domain="${CANONICAL_DOMAIN}"
|
||||
printf 'NODE_BUILDER_IMAGE=%s\n' "${NODE_BUILDER_IMAGE:-node:22-bookworm}"
|
||||
printf 'NODE_RUNTIME_IMAGE=%s\n' "${NODE_RUNTIME_IMAGE:-node:22-slim}"
|
||||
printf 'CONTENTLAYER_BUILD=%s\n' "${CONTENTLAYER_BUILD:-true}"
|
||||
printf 'NEXT_PUBLIC_APP_BASE_URL=%s\n' "${NEXT_PUBLIC_APP_BASE_URL:-https://${primary_domain}}"
|
||||
printf 'NEXT_PUBLIC_SITE_URL=%s\n' "${NEXT_PUBLIC_SITE_URL:-https://${primary_domain}}"
|
||||
printf 'NEXT_PUBLIC_LOGIN_URL=%s\n' "${NEXT_PUBLIC_LOGIN_URL:-https://${primary_domain}/login}"
|
||||
printf 'NEXT_PUBLIC_DOCS_BASE_URL=%s\n' "${NEXT_PUBLIC_DOCS_BASE_URL:-https://${primary_domain}/docs}"
|
||||
printf 'NEXT_PUBLIC_APP_BASE_URL=%s\n' "${NEXT_PUBLIC_APP_BASE_URL:-https://${canonical_domain}}"
|
||||
printf 'NEXT_PUBLIC_SITE_URL=%s\n' "${NEXT_PUBLIC_SITE_URL:-https://${canonical_domain}}"
|
||||
printf 'NEXT_PUBLIC_LOGIN_URL=%s\n' "${NEXT_PUBLIC_LOGIN_URL:-https://${canonical_domain}/login}"
|
||||
printf 'NEXT_PUBLIC_DOCS_BASE_URL=%s\n' "${NEXT_PUBLIC_DOCS_BASE_URL:-https://${canonical_domain}/docs}"
|
||||
printf 'NEXT_PUBLIC_RUNTIME_ENVIRONMENT=%s\n' "${NEXT_PUBLIC_RUNTIME_ENVIRONMENT:-prod}"
|
||||
printf 'NEXT_PUBLIC_RUNTIME_REGION=%s\n' "${NEXT_PUBLIC_RUNTIME_REGION:-cn}"
|
||||
printf 'NEXT_PUBLIC_GISCUS_REPO=%s\n' "${NEXT_PUBLIC_GISCUS_REPO:-x-evor/console.svc.plus}"
|
||||
|
||||
@ -22,25 +22,27 @@ require_env() {
|
||||
}
|
||||
|
||||
require_env FRONTEND_IMAGE
|
||||
require_env PRIMARY_DOMAIN
|
||||
require_env CANONICAL_DOMAIN
|
||||
require_env SERVED_DOMAINS
|
||||
|
||||
append_env FRONTEND_IMAGE "${FRONTEND_IMAGE}"
|
||||
append_env PRIMARY_DOMAIN "${PRIMARY_DOMAIN}"
|
||||
append_env CANONICAL_DOMAIN "${CANONICAL_DOMAIN}"
|
||||
append_env SERVED_DOMAINS "${SERVED_DOMAINS}"
|
||||
|
||||
append_env NODE_ENV "production"
|
||||
append_env PORT "${PORT:-3000}"
|
||||
append_env RUNTIME_ENV "${RUNTIME_ENV:-prod}"
|
||||
append_env REGION "${REGION:-cn}"
|
||||
append_env APP_BASE_URL "${APP_BASE_URL:-https://${PRIMARY_DOMAIN}}"
|
||||
append_env NEXT_PUBLIC_APP_BASE_URL "${NEXT_PUBLIC_APP_BASE_URL:-https://${PRIMARY_DOMAIN}}"
|
||||
append_env NEXT_PUBLIC_SITE_URL "${NEXT_PUBLIC_SITE_URL:-https://${PRIMARY_DOMAIN}}"
|
||||
append_env NEXT_PUBLIC_LOGIN_URL "${NEXT_PUBLIC_LOGIN_URL:-https://${PRIMARY_DOMAIN}/login}"
|
||||
append_env NEXT_PUBLIC_DOCS_BASE_URL "${NEXT_PUBLIC_DOCS_BASE_URL:-https://${PRIMARY_DOMAIN}/docs}"
|
||||
append_env APP_BASE_URL "${APP_BASE_URL:-https://${CANONICAL_DOMAIN}}"
|
||||
append_env NEXT_PUBLIC_APP_BASE_URL "${NEXT_PUBLIC_APP_BASE_URL:-https://${CANONICAL_DOMAIN}}"
|
||||
append_env NEXT_PUBLIC_SITE_URL "${NEXT_PUBLIC_SITE_URL:-https://${CANONICAL_DOMAIN}}"
|
||||
append_env NEXT_PUBLIC_LOGIN_URL "${NEXT_PUBLIC_LOGIN_URL:-https://${CANONICAL_DOMAIN}/login}"
|
||||
append_env NEXT_PUBLIC_DOCS_BASE_URL "${NEXT_PUBLIC_DOCS_BASE_URL:-https://${CANONICAL_DOMAIN}/docs}"
|
||||
append_env SESSION_COOKIE_SECURE "${SESSION_COOKIE_SECURE:-true}"
|
||||
append_env NEXT_PUBLIC_SESSION_COOKIE_SECURE "${NEXT_PUBLIC_SESSION_COOKIE_SECURE:-true}"
|
||||
append_env RUNTIME_HOSTNAME "${RUNTIME_HOSTNAME:-${PRIMARY_DOMAIN}}"
|
||||
append_env NEXT_RUNTIME_HOSTNAME "${NEXT_RUNTIME_HOSTNAME:-${PRIMARY_DOMAIN}}"
|
||||
append_env DEPLOYMENT_HOSTNAME "${DEPLOYMENT_HOSTNAME:-${PRIMARY_DOMAIN}}"
|
||||
append_env RUNTIME_HOSTNAME "${RUNTIME_HOSTNAME:-${CANONICAL_DOMAIN}}"
|
||||
append_env NEXT_RUNTIME_HOSTNAME "${NEXT_RUNTIME_HOSTNAME:-${CANONICAL_DOMAIN}}"
|
||||
append_env DEPLOYMENT_HOSTNAME "${DEPLOYMENT_HOSTNAME:-${CANONICAL_DOMAIN}}"
|
||||
append_env NEXT_PUBLIC_RUNTIME_ENVIRONMENT "${NEXT_PUBLIC_RUNTIME_ENVIRONMENT:-prod}"
|
||||
append_env NEXT_PUBLIC_RUNTIME_REGION "${NEXT_PUBLIC_RUNTIME_REGION:-cn}"
|
||||
append_env ACCOUNT_SERVICE_URL "${ACCOUNT_SERVICE_URL:-https://accounts.svc.plus}"
|
||||
|
||||
@ -15,7 +15,8 @@ ansible_args=(
|
||||
-e "GHCR_PASSWORD=${GHCR_PASSWORD:?GHCR_PASSWORD is required}"
|
||||
-e "INTERNAL_SERVICE_TOKEN=${INTERNAL_SERVICE_TOKEN:?INTERNAL_SERVICE_TOKEN is required}"
|
||||
-e "ACCOUNT_SERVICE_URL=${ACCOUNT_SERVICE_URL:?ACCOUNT_SERVICE_URL is required}"
|
||||
-e "PRIMARY_DOMAIN=${PRIMARY_DOMAIN:?PRIMARY_DOMAIN is required}"
|
||||
-e "CANONICAL_DOMAIN=${CANONICAL_DOMAIN:?CANONICAL_DOMAIN is required}"
|
||||
-e "SERVED_DOMAINS=${SERVED_DOMAINS:?SERVED_DOMAINS is required}"
|
||||
-e "NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT:?NEXT_PUBLIC_RUNTIME_ENVIRONMENT is required}"
|
||||
-e "NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION:?NEXT_PUBLIC_RUNTIME_REGION is required}"
|
||||
-e "CLOUDFLARE_ZONE_TAG=${CLOUDFLARE_ZONE_TAG:?CLOUDFLARE_ZONE_TAG is required}"
|
||||
|
||||
@ -1,25 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PRIMARY_DOMAIN="${1:?usage: verify-frontend-release.sh <primary-domain>}"
|
||||
CANONICAL_DOMAIN="${1:?usage: verify-frontend-release.sh <canonical-domain> <served-domains>}"
|
||||
SERVED_DOMAINS="${2:?usage: verify-frontend-release.sh <canonical-domain> <served-domains>}"
|
||||
EXPECTED_DASHBOARD_URL="https://${CANONICAL_DOMAIN}"
|
||||
|
||||
primary_url="https://${PRIMARY_DOMAIN}"
|
||||
curl_headers=(
|
||||
-H 'user-agent: Mozilla/5.0 (compatible; console-release-validator/1.0; +https://www.svc.plus)'
|
||||
-H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
||||
-H 'accept-language: en-US,en;q=0.9'
|
||||
)
|
||||
|
||||
curl -fsSIL "${primary_url}" >/dev/null
|
||||
trim() {
|
||||
local value="$1"
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
printf '%s' "${value}"
|
||||
}
|
||||
|
||||
homepage_html="$(curl -fsSL "${primary_url}")"
|
||||
asset_path="$(printf '%s' "${homepage_html}" | grep -Eo '/_next/static/[^"'"'"' ]+\.(css|js)' | head -n 1)"
|
||||
if [[ -z "${asset_path}" ]]; then
|
||||
echo "Could not find a _next/static asset on the homepage" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -fsSIL "${primary_url}${asset_path}" >/dev/null
|
||||
printf 'verified static asset: %s%s\n' "${primary_url}" "${asset_path}"
|
||||
|
||||
release_payload="$(curl -fsSL "${primary_url}/api/ping")"
|
||||
release_metadata="$(
|
||||
RELEASE_PAYLOAD="${release_payload}" python3 - <<'PY'
|
||||
parse_release_metadata() {
|
||||
local payload="$1"
|
||||
RELEASE_PAYLOAD="${payload}" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
@ -27,28 +28,141 @@ payload = json.loads(os.environ["RELEASE_PAYLOAD"])
|
||||
print(payload.get("releaseImageRef", ""))
|
||||
print(payload.get("releaseImageTag", ""))
|
||||
print(payload.get("releaseCommit", ""))
|
||||
print(payload.get("dashboardUrl", ""))
|
||||
PY
|
||||
)"
|
||||
}
|
||||
|
||||
mapfile -t release_lines <<< "${release_metadata}"
|
||||
actual_image_ref="${release_lines[0]-}"
|
||||
actual_image_tag="${release_lines[1]-}"
|
||||
actual_release_commit="${release_lines[2]-}"
|
||||
require_http_200() {
|
||||
local url="$1"
|
||||
shift
|
||||
|
||||
if [[ -z "${actual_image_ref}" || -z "${actual_image_tag}" || -z "${actual_release_commit}" ]]; then
|
||||
echo "Remote release metadata is incomplete: ${release_payload}" >&2
|
||||
local http_code
|
||||
http_code="$(curl -sS -o /dev/null -w '%{http_code}' "$@" "${url}")"
|
||||
if [[ "${http_code}" != "200" ]]; then
|
||||
echo "Expected HTTP 200 from ${url}, got ${http_code}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_domain() {
|
||||
local domain="$1"
|
||||
local url="https://${domain}"
|
||||
local homepage_html asset_path release_payload release_metadata
|
||||
local actual_image_ref actual_image_tag actual_release_commit actual_dashboard_url
|
||||
local release_lines
|
||||
|
||||
require_http_200 "${url}" "${curl_headers[@]}"
|
||||
printf 'verified homepage for %s: 200\n' "${domain}" >&2
|
||||
|
||||
homepage_html="$(curl -fsSL "${curl_headers[@]}" "${url}")"
|
||||
asset_path="$(printf '%s' "${homepage_html}" | grep -Eo '/_next/static/[^"'"'"' ]+\.(css|js)' | head -n 1)"
|
||||
if [[ -z "${asset_path}" ]]; then
|
||||
echo "Could not find a _next/static asset on ${url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_http_200 "${url}${asset_path}" "${curl_headers[@]}"
|
||||
printf 'verified static asset for %s: %s%s\n' "${domain}" "${url}" "${asset_path}" >&2
|
||||
|
||||
require_http_200 "${url}/api/ping" "${curl_headers[@]}"
|
||||
release_payload="$(curl -fsSL "${curl_headers[@]}" "${url}/api/ping")"
|
||||
release_metadata="$(parse_release_metadata "${release_payload}")"
|
||||
|
||||
mapfile -t release_lines <<< "${release_metadata}"
|
||||
actual_image_ref="${release_lines[0]-}"
|
||||
actual_image_tag="${release_lines[1]-}"
|
||||
actual_release_commit="${release_lines[2]-}"
|
||||
actual_dashboard_url="${release_lines[3]-}"
|
||||
|
||||
if [[ -z "${actual_image_ref}" || -z "${actual_image_tag}" || -z "${actual_release_commit}" ]]; then
|
||||
echo "Remote release metadata is incomplete for ${domain}: ${release_payload}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "${actual_image_tag}" =~ ^[0-9a-f]{7,40}$ ]]; then
|
||||
echo "Remote image tag must contain a commit id for ${domain}, got: ${actual_image_tag}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_release_commit}" != "${actual_image_tag}" ]]; then
|
||||
echo "Remote release commit mismatch for ${domain}: expected ${actual_image_tag}, got ${actual_release_commit}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_dashboard_url}" != "${EXPECTED_DASHBOARD_URL}" ]]; then
|
||||
echo "Remote dashboardUrl mismatch for ${domain}: expected ${EXPECTED_DASHBOARD_URL}, got ${actual_dashboard_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'verified release image for %s: %s\n' "${domain}" "${actual_image_ref}" >&2
|
||||
printf 'verified release commit for %s: %s\n' "${domain}" "${actual_release_commit}" >&2
|
||||
printf 'verified dashboardUrl for %s: %s\n' "${domain}" "${actual_dashboard_url}" >&2
|
||||
|
||||
printf '%s\t%s\t%s\t%s\t%s\n' "${domain}" "${actual_image_ref}" "${actual_image_tag}" "${actual_release_commit}" "${actual_dashboard_url}"
|
||||
}
|
||||
|
||||
mapfile -t served_domains < <(
|
||||
printf '%s' "${SERVED_DOMAINS}" | tr ',' '\n' | while IFS= read -r domain; do
|
||||
domain="$(trim "${domain}")"
|
||||
if [[ -n "${domain}" ]]; then
|
||||
printf '%s\n' "${domain}"
|
||||
fi
|
||||
done
|
||||
)
|
||||
|
||||
if [[ "${#served_domains[@]}" -eq 0 ]]; then
|
||||
echo "No served domains were provided." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "${actual_image_tag}" =~ ^[0-9a-f]{7,40}$ ]]; then
|
||||
echo "Remote image tag must contain a commit id, got: ${actual_image_tag}" >&2
|
||||
canonical_found=false
|
||||
declare -a verification_rows=()
|
||||
|
||||
for domain in "${served_domains[@]}"; do
|
||||
if [[ "${domain}" == "${CANONICAL_DOMAIN}" ]]; then
|
||||
canonical_found=true
|
||||
fi
|
||||
verification_rows+=("$(verify_domain "${domain}")")
|
||||
done
|
||||
|
||||
if [[ "${canonical_found}" != "true" ]]; then
|
||||
echo "Canonical domain ${CANONICAL_DOMAIN} must be included in served domains: ${SERVED_DOMAINS}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_release_commit}" != "${actual_image_tag}" ]]; then
|
||||
echo "Remote release commit mismatch: expected ${actual_image_tag}, got ${actual_release_commit}" >&2
|
||||
exit 1
|
||||
fi
|
||||
reference_image_ref=""
|
||||
reference_image_tag=""
|
||||
reference_release_commit=""
|
||||
reference_dashboard_url=""
|
||||
|
||||
printf 'verified release image: %s\n' "${actual_image_ref}"
|
||||
printf 'verified release commit: %s\n' "${actual_release_commit}"
|
||||
for row in "${verification_rows[@]}"; do
|
||||
IFS=$'\t' read -r domain actual_image_ref actual_image_tag actual_release_commit actual_dashboard_url <<< "${row}"
|
||||
|
||||
if [[ -z "${reference_image_ref}" ]]; then
|
||||
reference_image_ref="${actual_image_ref}"
|
||||
reference_image_tag="${actual_image_tag}"
|
||||
reference_release_commit="${actual_release_commit}"
|
||||
reference_dashboard_url="${actual_dashboard_url}"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "${actual_image_ref}" != "${reference_image_ref}" ]]; then
|
||||
echo "Release image mismatch across served domains: ${domain} returned ${actual_image_ref}, expected ${reference_image_ref}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_image_tag}" != "${reference_image_tag}" ]]; then
|
||||
echo "Release tag mismatch across served domains: ${domain} returned ${actual_image_tag}, expected ${reference_image_tag}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_release_commit}" != "${reference_release_commit}" ]]; then
|
||||
echo "Release commit mismatch across served domains: ${domain} returned ${actual_release_commit}, expected ${reference_release_commit}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${actual_dashboard_url}" != "${reference_dashboard_url}" ]]; then
|
||||
echo "dashboardUrl mismatch across served domains: ${domain} returned ${actual_dashboard_url}, expected ${reference_dashboard_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
@ -15,7 +15,7 @@ const DEFAULT_DESCRIPTION =
|
||||
const DEFAULT_OG_IMAGE = '/icons/webchat.jpg'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://console.svc.plus'),
|
||||
metadataBase: new URL('https://www.svc.plus'),
|
||||
title: {
|
||||
default: DEFAULT_TITLE,
|
||||
template: '%s | Cloud-Neutral',
|
||||
@ -93,8 +93,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: 'Cloud-Neutral',
|
||||
url: 'https://console.svc.plus',
|
||||
logo: 'https://console.svc.plus/icons/cloudnative_32.png',
|
||||
url: 'https://www.svc.plus',
|
||||
logo: 'https://www.svc.plus/icons/cloudnative_32.png',
|
||||
description: DEFAULT_DESCRIPTION,
|
||||
}).replace(/</g, '\\u003c'),
|
||||
}}
|
||||
@ -106,7 +106,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'Cloud-Neutral',
|
||||
url: 'https://console.svc.plus',
|
||||
url: 'https://www.svc.plus',
|
||||
description: DEFAULT_DESCRIPTION,
|
||||
}).replace(/</g, '\\u003c'),
|
||||
}}
|
||||
|
||||
@ -3,7 +3,7 @@ import type { MetadataRoute } from 'next'
|
||||
import { getBlogList } from '@/lib/docsServiceClient'
|
||||
import { PRODUCT_LIST } from '@/modules/products/registry'
|
||||
|
||||
const baseUrl = 'https://console.svc.plus'
|
||||
const baseUrl = 'https://www.svc.plus'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const revalidate = 3600 // Revalidate every hour
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Base runtime configuration shared by all environments.
|
||||
apiBaseUrl: https://rag-server-svc-plus-266500572462.asia-northeast1.run.app
|
||||
authUrl: https://accounts.svc.plus
|
||||
dashboardUrl: https://console.svc.plus
|
||||
dashboardUrl: https://www.svc.plus
|
||||
docsServiceUrl: https://docs.svc.plus
|
||||
logLevel: info
|
||||
|
||||
@ -4,10 +4,10 @@ regions:
|
||||
cn:
|
||||
apiBaseUrl: https://rag-server-svc-plus-266500572462.asia-northeast1.run.app
|
||||
authUrl: https://accounts.svc.plus
|
||||
dashboardUrl: https://console.svc.plus
|
||||
dashboardUrl: https://www.svc.plus
|
||||
docsServiceUrl: https://docs.svc.plus
|
||||
global:
|
||||
apiBaseUrl: https://rag-server-svc-plus-266500572462.asia-northeast1.run.app
|
||||
authUrl: https://accounts.svc.plus
|
||||
dashboardUrl: https://console.svc.plus
|
||||
dashboardUrl: https://www.svc.plus
|
||||
docsServiceUrl: https://docs.svc.plus
|
||||
|
||||
@ -24,6 +24,6 @@ describe("runtime-loader", () => {
|
||||
const config = loadRuntimeConfig({ hostname: "console.svc.plus" });
|
||||
|
||||
expect(config.authUrl).toBe("https://accounts.svc.plus");
|
||||
expect(config.dashboardUrl).toBe("https://console.svc.plus");
|
||||
expect(config.dashboardUrl).toBe("https://www.svc.plus");
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user