feat(console): add single-node frontend release flow
This commit is contained in:
parent
f7041a1410
commit
fea1ab6640
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@ -0,0 +1,15 @@
|
||||
.git
|
||||
.github
|
||||
.next
|
||||
.contentlayer
|
||||
node_modules
|
||||
coverage
|
||||
dist
|
||||
build
|
||||
test-results
|
||||
*.log
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
deploy/single-node/.env.runtime
|
||||
knowledge/.git
|
||||
25
.env.example
25
.env.example
@ -1,3 +1,26 @@
|
||||
# Frontend site base URLs
|
||||
APP_BASE_URL=
|
||||
NEXT_PUBLIC_APP_BASE_URL=
|
||||
NEXT_PUBLIC_SITE_URL=
|
||||
NEXT_PUBLIC_LOGIN_URL=
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=
|
||||
SESSION_COOKIE_SECURE=true
|
||||
NEXT_PUBLIC_SESSION_COOKIE_SECURE=true
|
||||
RUNTIME_HOSTNAME=
|
||||
NEXT_RUNTIME_HOSTNAME=
|
||||
DEPLOYMENT_HOSTNAME=
|
||||
RUNTIME_ENV=prod
|
||||
REGION=cn
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=prod
|
||||
NEXT_PUBLIC_RUNTIME_REGION=cn
|
||||
|
||||
# Upstream service endpoints
|
||||
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
NEXT_PUBLIC_ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
SERVER_SERVICE_URL=https://api.svc.plus
|
||||
NEXT_PUBLIC_SERVER_SERVICE_URL=https://api.svc.plus
|
||||
SERVER_SERVICE_INTERNAL_URL=
|
||||
|
||||
# OpenClaw assistant integrations
|
||||
# Use environment variables to prefill the assistant and integrations page.
|
||||
# Values are read server-side and are not hardcoded into the UI.
|
||||
@ -23,6 +46,7 @@ INTERNAL_SERVICE_TOKEN=
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
|
||||
CLOUDFLARE_ZONE_TAG=
|
||||
|
||||
# Root email whitelist for privileged user-creation actions (comma-separated)
|
||||
# Default: admin@svc.plus
|
||||
@ -30,6 +54,7 @@ ROOT_EMAIL_WHITELIST=admin@svc.plus
|
||||
|
||||
# Stripe public price ids used by /prices, product pages, and /panel/subscription
|
||||
# These values are safe to expose to the browser. Use Stripe test-mode price ids for local/dev.
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
|
||||
|
||||
175
.github/workflows/service_release_frontend-deploy.yml
vendored
Normal file
175
.github/workflows/service_release_frontend-deploy.yml
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
name: Service Release Frontend Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image_tag:
|
||||
description: Optional image tag override. Defaults to the current commit SHA.
|
||||
required: false
|
||||
type: string
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/service_release_frontend-deploy.yml"
|
||||
- "deploy/single-node/**"
|
||||
- "scripts/github-actions/**"
|
||||
- "src/**"
|
||||
- "public/**"
|
||||
- "scripts/**"
|
||||
- "config/**"
|
||||
- "package.json"
|
||||
- "Dockerfile"
|
||||
- ".env.example"
|
||||
- "next.config.mjs"
|
||||
- "tailwind.config.js"
|
||||
- "postcss.config.mjs"
|
||||
- "tsconfig.json"
|
||||
- "contentlayer.config.ts"
|
||||
|
||||
concurrency:
|
||||
group: frontend-prod
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
env:
|
||||
DEPLOY_HOST: 47.120.61.35
|
||||
DEPLOY_USER: root
|
||||
DEPLOY_DIR: /opt/console-svc-plus
|
||||
PRIMARY_DOMAIN: cn.svc.plus
|
||||
SECONDARY_DOMAIN: cn.onwalk.net
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ghcr_namespace: ${{ steps.meta.outputs.ghcr_namespace }}
|
||||
image_tag: ${{ steps.meta.outputs.image_tag }}
|
||||
image_ref: ${{ steps.meta.outputs.image_ref }}
|
||||
steps:
|
||||
- name: Compute image metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
image_tag="${{ github.event.inputs.image_tag }}"
|
||||
if [[ -z "${image_tag}" ]]; then
|
||||
image_tag="${GITHUB_SHA}"
|
||||
fi
|
||||
ghcr_namespace="${GITHUB_REPOSITORY_OWNER,,}"
|
||||
echo "ghcr_namespace=${ghcr_namespace}" >> "${GITHUB_OUTPUT}"
|
||||
echo "image_tag=${image_tag}" >> "${GITHUB_OUTPUT}"
|
||||
echo "image_ref=ghcr.io/${ghcr_namespace}/dashboard:${image_tag}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare
|
||||
environment: production
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Clone knowledge content
|
||||
run: git clone --depth=1 https://github.com/Cloud-Neutral-Workshop/knowledge.git knowledge
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push frontend image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ needs.prepare.outputs.image_ref }}
|
||||
build-args: |
|
||||
NODE_BUILDER_IMAGE=node:22-bookworm
|
||||
NODE_RUNTIME_IMAGE=node:22-slim
|
||||
CONTENTLAYER_BUILD=true
|
||||
NEXT_PUBLIC_APP_BASE_URL=${{ vars.NEXT_PUBLIC_APP_BASE_URL || format('https://{0}', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_SITE_URL=${{ vars.NEXT_PUBLIC_SITE_URL || format('https://{0}', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_LOGIN_URL=${{ vars.NEXT_PUBLIC_LOGIN_URL || format('https://{0}/login', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=${{ vars.NEXT_PUBLIC_DOCS_BASE_URL || format('https://{0}/docs', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${{ vars.NEXT_PUBLIC_RUNTIME_ENVIRONMENT || 'prod' }}
|
||||
NEXT_PUBLIC_RUNTIME_REGION=${{ vars.NEXT_PUBLIC_RUNTIME_REGION || 'cn' }}
|
||||
NEXT_PUBLIC_GISCUS_REPO=${{ vars.NEXT_PUBLIC_GISCUS_REPO || 'cloud-neutral-toolkit/console.svc.plus' }}
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID=${{ vars.NEXT_PUBLIC_GISCUS_REPO_ID }}
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY=${{ vars.NEXT_PUBLIC_GISCUS_CATEGORY || 'General' }}
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID=${{ vars.NEXT_PUBLIC_GISCUS_CATEGORY_ID }}
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=${{ vars.NEXT_PUBLIC_PAYPAL_CLIENT_ID }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION }}
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
- build
|
||||
environment: production
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy frontend stack
|
||||
env:
|
||||
GHCR_USERNAME: ${{ github.actor }}
|
||||
GHCR_PASSWORD: ${{ github.token }}
|
||||
SSH_PRIVATE_KEY: ${{ secrets.FRONTEND_DEPLOY_SSH_KEY }}
|
||||
FRONTEND_IMAGE: ${{ needs.prepare.outputs.image_ref }}
|
||||
APP_BASE_URL: ${{ vars.APP_BASE_URL || format('https://{0}', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_APP_BASE_URL: ${{ vars.NEXT_PUBLIC_APP_BASE_URL || format('https://{0}', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_SITE_URL: ${{ vars.NEXT_PUBLIC_SITE_URL || format('https://{0}', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_LOGIN_URL: ${{ vars.NEXT_PUBLIC_LOGIN_URL || format('https://{0}/login', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_DOCS_BASE_URL: ${{ vars.NEXT_PUBLIC_DOCS_BASE_URL || format('https://{0}/docs', env.PRIMARY_DOMAIN) }}
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_RUNTIME_ENVIRONMENT || 'prod' }}
|
||||
NEXT_PUBLIC_RUNTIME_REGION: ${{ vars.NEXT_PUBLIC_RUNTIME_REGION || 'cn' }}
|
||||
RUNTIME_HOSTNAME: ${{ vars.RUNTIME_HOSTNAME || env.PRIMARY_DOMAIN }}
|
||||
NEXT_RUNTIME_HOSTNAME: ${{ vars.NEXT_RUNTIME_HOSTNAME || env.PRIMARY_DOMAIN }}
|
||||
DEPLOYMENT_HOSTNAME: ${{ vars.DEPLOYMENT_HOSTNAME || env.PRIMARY_DOMAIN }}
|
||||
ACCOUNT_SERVICE_URL: ${{ vars.ACCOUNT_SERVICE_URL || 'https://accounts.svc.plus' }}
|
||||
NEXT_PUBLIC_ACCOUNT_SERVICE_URL: ${{ vars.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || vars.ACCOUNT_SERVICE_URL || 'https://accounts.svc.plus' }}
|
||||
SERVER_SERVICE_URL: ${{ vars.SERVER_SERVICE_URL || 'https://api.svc.plus' }}
|
||||
NEXT_PUBLIC_SERVER_SERVICE_URL: ${{ vars.NEXT_PUBLIC_SERVER_SERVICE_URL || vars.SERVER_SERVICE_URL || 'https://api.svc.plus' }}
|
||||
SERVER_SERVICE_INTERNAL_URL: ${{ vars.SERVER_SERVICE_INTERNAL_URL }}
|
||||
ROOT_EMAIL_WHITELIST: ${{ vars.ROOT_EMAIL_WHITELIST || 'admin@svc.plus' }}
|
||||
OPENCLAW_GATEWAY_REMOTE_URL: ${{ vars.OPENCLAW_GATEWAY_REMOTE_URL }}
|
||||
OPENCLAW_GATEWAY_TOKEN: ${{ secrets.OPENCLAW_GATEWAY_TOKEN }}
|
||||
VAULT_SERVER_URL: ${{ vars.VAULT_SERVER_URL }}
|
||||
VAULT_NAMESPACE: ${{ vars.VAULT_NAMESPACE }}
|
||||
VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }}
|
||||
APISIX_AI_GATEWAY_URL: ${{ vars.APISIX_AI_GATEWAY_URL }}
|
||||
AI_GATEWAY_ACCESS_TOKEN: ${{ secrets.AI_GATEWAY_ACCESS_TOKEN }}
|
||||
INTERNAL_SERVICE_TOKEN: ${{ secrets.INTERNAL_SERVICE_TOKEN }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG: ${{ vars.CLOUDFLARE_WEB_ANALYTICS_SITE_TAG }}
|
||||
CLOUDFLARE_ZONE_TAG: ${{ vars.CLOUDFLARE_ZONE_TAG }}
|
||||
NEXT_PUBLIC_GISCUS_REPO: ${{ vars.NEXT_PUBLIC_GISCUS_REPO || 'cloud-neutral-toolkit/console.svc.plus' }}
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID: ${{ vars.NEXT_PUBLIC_GISCUS_REPO_ID }}
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY: ${{ vars.NEXT_PUBLIC_GISCUS_CATEGORY || 'General' }}
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID: ${{ vars.NEXT_PUBLIC_GISCUS_CATEGORY_ID }}
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID: ${{ vars.NEXT_PUBLIC_PAYPAL_CLIENT_ID }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO: ${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION: ${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO: ${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION: ${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO: ${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION: ${{ vars.NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION }}
|
||||
run: bash scripts/github-actions/deploy-frontend-single-node.sh
|
||||
|
||||
- name: Verify primary domain
|
||||
run: curl -fsSIL "https://${PRIMARY_DOMAIN}"
|
||||
|
||||
- name: Verify secondary domain redirect
|
||||
run: curl -fsSIL "https://${SECONDARY_DOMAIN}"
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -55,6 +55,7 @@ coverage/
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
deploy/single-node/.env.runtime
|
||||
|
||||
# Build artifacts
|
||||
build/
|
||||
|
||||
55
Dockerfile
55
Dockerfile
@ -4,6 +4,23 @@
|
||||
ARG NODE_BUILDER_IMAGE=node:22-bookworm
|
||||
ARG NODE_RUNTIME_IMAGE=node:22-slim
|
||||
ARG CONTENTLAYER_BUILD=true
|
||||
ARG NEXT_PUBLIC_APP_BASE_URL=
|
||||
ARG NEXT_PUBLIC_SITE_URL=
|
||||
ARG NEXT_PUBLIC_LOGIN_URL=
|
||||
ARG NEXT_PUBLIC_DOCS_BASE_URL=
|
||||
ARG NEXT_PUBLIC_RUNTIME_ENVIRONMENT=
|
||||
ARG NEXT_PUBLIC_RUNTIME_REGION=
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO=
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO_ID=
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY=
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY_ID=
|
||||
ARG NEXT_PUBLIC_PAYPAL_CLIENT_ID=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Stage 1 — Builder (Turbopack + standalone)
|
||||
@ -12,8 +29,43 @@ FROM ${NODE_BUILDER_IMAGE} AS builder
|
||||
|
||||
WORKDIR /app/dashboard
|
||||
|
||||
ARG NEXT_PUBLIC_APP_BASE_URL
|
||||
ARG NEXT_PUBLIC_SITE_URL
|
||||
ARG NEXT_PUBLIC_LOGIN_URL
|
||||
ARG NEXT_PUBLIC_DOCS_BASE_URL
|
||||
ARG NEXT_PUBLIC_RUNTIME_ENVIRONMENT
|
||||
ARG NEXT_PUBLIC_RUNTIME_REGION
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO_ID
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY_ID
|
||||
ARG NEXT_PUBLIC_PAYPAL_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1 \
|
||||
NEXT_PRIVATE_TURBOPACK=1
|
||||
NEXT_PRIVATE_TURBOPACK=1 \
|
||||
NEXT_PUBLIC_APP_BASE_URL=${NEXT_PUBLIC_APP_BASE_URL} \
|
||||
NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL} \
|
||||
NEXT_PUBLIC_LOGIN_URL=${NEXT_PUBLIC_LOGIN_URL} \
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=${NEXT_PUBLIC_DOCS_BASE_URL} \
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT} \
|
||||
NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION} \
|
||||
NEXT_PUBLIC_GISCUS_REPO=${NEXT_PUBLIC_GISCUS_REPO} \
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID=${NEXT_PUBLIC_GISCUS_REPO_ID} \
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY=${NEXT_PUBLIC_GISCUS_CATEGORY} \
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID=${NEXT_PUBLIC_GISCUS_CATEGORY_ID} \
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=${NEXT_PUBLIC_PAYPAL_CLIENT_ID} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION}
|
||||
|
||||
# ---------------------------
|
||||
# 基础镜像升级到最新
|
||||
@ -59,6 +111,7 @@ RUN apt-get update \
|
||||
COPY --from=builder /app/dashboard/.next/standalone ./
|
||||
COPY --from=builder /app/dashboard/.next/static ./static
|
||||
COPY --from=builder /app/dashboard/public ./public
|
||||
COPY --from=builder /app/dashboard/knowledge ./knowledge
|
||||
COPY --from=builder /app/dashboard/src/content/blog ./src/content/blog
|
||||
|
||||
# ---------------------------
|
||||
|
||||
54
deploy/single-node/.env.runtime.example
Normal file
54
deploy/single-node/.env.runtime.example
Normal file
@ -0,0 +1,54 @@
|
||||
# Compose settings
|
||||
FRONTEND_IMAGE=ghcr.io/cloud-neutral-toolkit/dashboard:replace-me
|
||||
PRIMARY_DOMAIN=cn.svc.plus
|
||||
SECONDARY_DOMAIN=cn.onwalk.net
|
||||
|
||||
# Frontend runtime
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
RUNTIME_ENV=prod
|
||||
REGION=cn
|
||||
APP_BASE_URL=https://cn.svc.plus
|
||||
NEXT_PUBLIC_APP_BASE_URL=https://cn.svc.plus
|
||||
NEXT_PUBLIC_SITE_URL=https://cn.svc.plus
|
||||
NEXT_PUBLIC_LOGIN_URL=https://cn.svc.plus/login
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=https://cn.svc.plus/docs
|
||||
SESSION_COOKIE_SECURE=true
|
||||
NEXT_PUBLIC_SESSION_COOKIE_SECURE=true
|
||||
RUNTIME_HOSTNAME=cn.svc.plus
|
||||
DEPLOYMENT_HOSTNAME=cn.svc.plus
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=prod
|
||||
NEXT_PUBLIC_RUNTIME_REGION=cn
|
||||
|
||||
# Upstream service URLs
|
||||
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
NEXT_PUBLIC_ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
SERVER_SERVICE_URL=https://api.svc.plus
|
||||
NEXT_PUBLIC_SERVER_SERVICE_URL=https://api.svc.plus
|
||||
SERVER_SERVICE_INTERNAL_URL=
|
||||
|
||||
# Optional integrations
|
||||
OPENCLAW_GATEWAY_REMOTE_URL=
|
||||
OPENCLAW_GATEWAY_TOKEN=
|
||||
VAULT_SERVER_URL=
|
||||
VAULT_NAMESPACE=
|
||||
VAULT_TOKEN=
|
||||
APISIX_AI_GATEWAY_URL=
|
||||
AI_GATEWAY_ACCESS_TOKEN=
|
||||
INTERNAL_SERVICE_TOKEN=
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
|
||||
CLOUDFLARE_ZONE_TAG=
|
||||
ROOT_EMAIL_WHITELIST=admin@svc.plus
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=
|
||||
NEXT_PUBLIC_GISCUS_REPO=cloud-neutral-toolkit/console.svc.plus
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID=
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY=General
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=
|
||||
32
deploy/single-node/Caddyfile
Normal file
32
deploy/single-node/Caddyfile
Normal file
@ -0,0 +1,32 @@
|
||||
{$PRIMARY_DOMAIN}, {$SECONDARY_DOMAIN} {
|
||||
encode zstd gzip
|
||||
|
||||
@secondary host {$SECONDARY_DOMAIN}
|
||||
redir @secondary https://{$PRIMARY_DOMAIN}{uri} permanent
|
||||
|
||||
@next_static path /_next/static/*
|
||||
handle @next_static {
|
||||
root * /srv
|
||||
header Cache-Control "public, max-age=31536000, immutable"
|
||||
file_server
|
||||
}
|
||||
|
||||
@public_assets {
|
||||
file {
|
||||
root /srv/public
|
||||
try_files {path}
|
||||
}
|
||||
}
|
||||
handle @public_assets {
|
||||
root * /srv/public
|
||||
header Cache-Control "public, max-age=3600"
|
||||
file_server
|
||||
}
|
||||
|
||||
reverse_proxy dashboard:3000 {
|
||||
header_up Host {host}
|
||||
header_up X-Forwarded-Host {host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
}
|
||||
}
|
||||
54
deploy/single-node/docker-compose.yml
Normal file
54
deploy/single-node/docker-compose.yml
Normal file
@ -0,0 +1,54 @@
|
||||
services:
|
||||
frontend-assets:
|
||||
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
|
||||
restart: "no"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
rm -rf /assets/_next /assets/public
|
||||
mkdir -p /assets/_next /assets/public
|
||||
cp -R /app/dashboard/static /assets/_next/static
|
||||
cp -R /app/dashboard/public/. /assets/public
|
||||
volumes:
|
||||
- frontend_static:/assets
|
||||
|
||||
dashboard:
|
||||
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env.runtime
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
caddy:
|
||||
image: caddy:2.10-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- dashboard
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
PRIMARY_DOMAIN: ${PRIMARY_DOMAIN:?set PRIMARY_DOMAIN in .env.runtime}
|
||||
SECONDARY_DOMAIN: ${SECONDARY_DOMAIN:?set SECONDARY_DOMAIN in .env.runtime}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- frontend_static:/srv:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
frontend_static:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
@ -1,31 +1,28 @@
|
||||
# Deployment
|
||||
|
||||
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
|
||||
## Production Baseline
|
||||
|
||||
Use this page to standardize deployment prerequisites, supported topologies, operational checks, and rollback notes.
|
||||
- Runtime: `Caddy + Docker Compose`
|
||||
- Deploy host: `47.120.61.35`
|
||||
- Domains:
|
||||
- `cn.svc.plus`
|
||||
- `cn.onwalk.net`
|
||||
- Frontend release workflow: `.github/workflows/service_release_frontend-deploy.yml`
|
||||
|
||||
## Current code-aligned notes
|
||||
## Operating Model
|
||||
|
||||
- Documentation target: `console.svc.plus`
|
||||
- Repo kind: `frontend`
|
||||
- Manifest and build evidence: package.json (`dashboard`)
|
||||
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
|
||||
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.
|
||||
|
||||
## Existing docs to reconcile
|
||||
The stack is static-first:
|
||||
|
||||
- Caddy serves `/_next/static/*` and public assets from a shared volume.
|
||||
- The Next.js standalone container serves dynamic HTML, auth endpoints, and API proxy routes.
|
||||
- `knowledge/` is cloned in CI and packed into the image during the Docker build.
|
||||
|
||||
This baseline is intentional for the current weak-IO single-node host. If `docs.svc.plus` becomes an API-backed service later, update this page and the runbook to remove docs payload from the frontend image.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- `development/dev-setup.md`
|
||||
- `getting-started/installation.md`
|
||||
- `getting-started/quickstart.md`
|
||||
- `governance/release-process.md`
|
||||
- `operations/runbooks/README.md`
|
||||
- `operations/runbooks/rag-server.md`
|
||||
- `usage/deployment.md`
|
||||
- `zh/development/dev-setup.md`
|
||||
|
||||
## What this page should cover next
|
||||
|
||||
- Describe the current implementation rather than an aspirational future-only design.
|
||||
- Keep terminology aligned with the repository root README, manifests, and actual directories.
|
||||
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
|
||||
- Verify deployment steps against current scripts, manifests, CI/CD flow, and environment contracts before each release.
|
||||
- `governance/release-process.md`
|
||||
- `development/dev-setup.md`
|
||||
|
||||
281
docs/plans/2026-03-18-frontend-single-node-deploy.md
Normal file
281
docs/plans/2026-03-18-frontend-single-node-deploy.md
Normal file
@ -0,0 +1,281 @@
|
||||
# Console Frontend Single-Node Deployment Design
|
||||
|
||||
## Scope
|
||||
|
||||
- Repository: `console.svc.plus`
|
||||
- Target host: `root@47.120.61.35`
|
||||
- Public domains:
|
||||
- `cn.svc.plus`
|
||||
- `cn.onwalk.net`
|
||||
- Delivery mode: `GitHub Actions + GHCR + Caddy + Docker Compose`
|
||||
|
||||
This document defines the deployment baseline for the China-facing frontend node. The source of truth is this upstream repository. The control-plane repository may consume the repo through git submodule, but should not become the primary place where this deployment design lives.
|
||||
|
||||
## Objective
|
||||
|
||||
Provide an independent frontend deployment pipeline for `console.svc.plus` that fits the current host constraints:
|
||||
|
||||
- the host IO is weak
|
||||
- the host must not build Docker images locally
|
||||
- the frontend should run in a static-first mode where possible
|
||||
- deployment logic should stay in checked-in scripts, not be embedded in GitHub Actions YAML
|
||||
|
||||
The result should support repeatable releases, quick rollback by image tag, and minimal work on the target machine.
|
||||
|
||||
## Constraints
|
||||
|
||||
### Host constraints
|
||||
|
||||
- `47.120.61.35` is a single-node host
|
||||
- deployment user is `root`
|
||||
- local image build on the host is explicitly disallowed
|
||||
- IO pressure should be minimized during release
|
||||
|
||||
### Application constraints
|
||||
|
||||
- `console.svc.plus` is not a purely static site
|
||||
- auth routes, same-origin API proxy routes, and selected dynamic pages still require a running Next.js server
|
||||
- some `NEXT_PUBLIC_*` variables are compiled into the frontend bundle at image build time
|
||||
- `prebuild` pulls documentation and `knowledge` content, so CI must prepare those inputs before building the image
|
||||
|
||||
### Repository constraints
|
||||
|
||||
- workflow YAML should remain orchestration-only
|
||||
- service-local operational notes should remain in this repo
|
||||
- downstream control repos can reference this repo through submodule updates after upstream changes are pushed
|
||||
|
||||
## Recommended Topology
|
||||
|
||||
### 1. CI build on GitHub Actions
|
||||
|
||||
The workflow builds a single `linux/amd64` image in GitHub Actions and pushes it to GHCR.
|
||||
|
||||
Reasons:
|
||||
|
||||
- matches the target host architecture
|
||||
- avoids multi-arch overhead for this single-node release path
|
||||
- avoids local host build IO and CPU pressure
|
||||
- keeps release artifacts immutable and rollback-friendly
|
||||
|
||||
### 2. Runtime on the host
|
||||
|
||||
Use `docker compose` with three services:
|
||||
|
||||
- `dashboard`: Next.js standalone runtime
|
||||
- `frontend-assets`: one-shot container that copies static files from the image into a Docker volume
|
||||
- `caddy`: TLS termination, redirect handling, static file serving, and reverse proxy
|
||||
|
||||
This keeps the host work limited to:
|
||||
|
||||
- image pull
|
||||
- asset extraction from the image
|
||||
- container restart
|
||||
|
||||
### 3. Static-first request flow
|
||||
|
||||
Caddy serves:
|
||||
|
||||
- `/_next/static/*`
|
||||
- checked-in `public/` assets
|
||||
|
||||
Next.js serves:
|
||||
|
||||
- HTML responses
|
||||
- `/api/*` routes
|
||||
- auth/session flows
|
||||
- dynamic pages that still depend on server runtime
|
||||
|
||||
This reduces repeat disk reads and network hops for the bulk of frontend traffic while preserving the dynamic behavior the app still needs.
|
||||
|
||||
## Build-Time vs Runtime Configuration
|
||||
|
||||
### Build-time config
|
||||
|
||||
These values must be available during Docker build because the frontend bundle reads them directly:
|
||||
|
||||
- `NEXT_PUBLIC_APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_SITE_URL`
|
||||
- `NEXT_PUBLIC_LOGIN_URL`
|
||||
- `NEXT_PUBLIC_DOCS_BASE_URL`
|
||||
- `NEXT_PUBLIC_RUNTIME_ENVIRONMENT`
|
||||
- `NEXT_PUBLIC_RUNTIME_REGION`
|
||||
- `NEXT_PUBLIC_GISCUS_*`
|
||||
- `NEXT_PUBLIC_PAYPAL_CLIENT_ID`
|
||||
- `NEXT_PUBLIC_STRIPE_*`
|
||||
|
||||
These are injected in GitHub Actions as Docker build args.
|
||||
|
||||
### Runtime config
|
||||
|
||||
These values are rendered into `.env.runtime` and copied to the host:
|
||||
|
||||
- upstream service URLs such as `ACCOUNT_SERVICE_URL`
|
||||
- tokens used only on the server side
|
||||
- Cloudflare analytics credentials
|
||||
- internal service token
|
||||
- runtime hostname hints
|
||||
|
||||
This separation avoids rebuilding for purely server-side secret or endpoint changes when the public frontend bundle does not change.
|
||||
|
||||
## Knowledge and Docs Handling
|
||||
|
||||
Current decision:
|
||||
|
||||
- `knowledge/` is cloned during CI
|
||||
- the cloned content is included in the image build context
|
||||
- the built image contains the resulting content needed by the current frontend
|
||||
|
||||
Reason:
|
||||
|
||||
- `prebuild` depends on this material
|
||||
- the host should not fetch or generate content during deployment
|
||||
|
||||
Temporary nature:
|
||||
|
||||
- today the frontend still carries docs-related payload
|
||||
- later, when `docs.svc.plus` becomes an API/service, docs delivery should move out of the frontend image
|
||||
- that future change should reduce image size and simplify the runtime responsibilities of `console.svc.plus`
|
||||
|
||||
## Domain Handling
|
||||
|
||||
Primary domain:
|
||||
|
||||
- `cn.svc.plus`
|
||||
|
||||
Secondary domain:
|
||||
|
||||
- `cn.onwalk.net`
|
||||
|
||||
Current routing decision:
|
||||
|
||||
- Caddy accepts both domains
|
||||
- requests for `cn.onwalk.net` are redirected permanently to `cn.svc.plus`
|
||||
|
||||
Reason:
|
||||
|
||||
- avoid duplicate canonical origins
|
||||
- keep cookie and login behavior centered on one primary host
|
||||
- simplify SEO and observability interpretation
|
||||
|
||||
## Release Workflow
|
||||
|
||||
### Trigger
|
||||
|
||||
Independent workflow:
|
||||
|
||||
- `.github/workflows/service_release_frontend-deploy.yml`
|
||||
|
||||
### Steps
|
||||
|
||||
1. check out repository
|
||||
2. clone `knowledge`
|
||||
3. build and push `ghcr.io/<owner>/dashboard:<tag>`
|
||||
4. render `.env.runtime`
|
||||
5. upload compose/caddy/env files to the host
|
||||
6. log in to GHCR on the host
|
||||
7. pull the new image
|
||||
8. run `frontend-assets`
|
||||
9. start or refresh `dashboard` and `caddy`
|
||||
10. verify both domains
|
||||
|
||||
### Why separate from the existing image workflow
|
||||
|
||||
The existing image workflow is broader and oriented toward generic image publishing. This single-node frontend workflow needs tighter control over:
|
||||
|
||||
- build-time public env injection
|
||||
- production deployment sequencing
|
||||
- SSH-based single-host rollout
|
||||
- host-specific runtime file rendering
|
||||
|
||||
So the frontend release path should remain explicit and independent.
|
||||
|
||||
## Rollback Model
|
||||
|
||||
Rollback unit:
|
||||
|
||||
- image tag reference in `.env.runtime`
|
||||
|
||||
Rollback steps:
|
||||
|
||||
1. set `FRONTEND_IMAGE` to a previous known-good tag
|
||||
2. rerun `frontend-assets`
|
||||
3. restart `dashboard` and `caddy`
|
||||
4. verify `cn.svc.plus`
|
||||
|
||||
This avoids rebuilding and keeps rollback cheap on the weak-IO host.
|
||||
|
||||
## Security and Secret Handling
|
||||
|
||||
Secrets must not be committed to the repo. The workflow should consume:
|
||||
|
||||
- `FRONTEND_DEPLOY_SSH_KEY`
|
||||
- service tokens
|
||||
- vault tokens
|
||||
- internal service token
|
||||
- optional Cloudflare credentials
|
||||
|
||||
Public defaults and non-secret values belong in checked-in examples or GitHub repository/environment variables. Secret-only values stay in GitHub Secrets and are rendered into the host runtime env during deployment.
|
||||
|
||||
## Operational Risks
|
||||
|
||||
### Risk 1: build-time public env mismatch
|
||||
|
||||
If GitHub environment variables are incomplete, the image may build successfully but the frontend can render wrong links or lose third-party integration IDs.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- keep `.env.example` aligned
|
||||
- document required GitHub `vars`
|
||||
- keep the build args list explicit
|
||||
|
||||
### Risk 2: image layout drift
|
||||
|
||||
If the Docker image no longer contains `/app/dashboard/static` or `/app/dashboard/public`, the `frontend-assets` step fails.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- keep asset extraction paths documented
|
||||
- update deploy scripts whenever Dockerfile output layout changes
|
||||
|
||||
### Risk 3: docs payload growth
|
||||
|
||||
Bundling docs and `knowledge` into the frontend image increases image size.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- accept it temporarily
|
||||
- revisit once `docs.svc.plus` is externalized
|
||||
|
||||
### Risk 4: single-node blast radius
|
||||
|
||||
The host handles both reverse proxy and app runtime. Misconfiguration affects the whole frontend surface.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- keep compose simple
|
||||
- keep Caddy config minimal
|
||||
- use image-tag rollback
|
||||
|
||||
## Future Follow-Up
|
||||
|
||||
### Near term
|
||||
|
||||
- populate required GitHub `vars` and `secrets`
|
||||
- run the workflow against `47.120.61.35`
|
||||
- validate DNS, TLS, static assets, login flow, and upstream API proxy behavior
|
||||
|
||||
### Later
|
||||
|
||||
- move docs delivery out of the frontend image after `docs.svc.plus` is service/API based
|
||||
- consider splitting static assets to object storage or CDN if traffic grows
|
||||
- evaluate whether the host should keep only Caddy plus one app container, or whether docs can be removed entirely from this runtime
|
||||
|
||||
## Source of Truth Rule
|
||||
|
||||
For this deployment design:
|
||||
|
||||
- upstream repo source of truth: `console.svc.plus`
|
||||
- service-local design note location: `docs/plans/`
|
||||
- control-plane repo role: consume via git submodule after upstream commit is pushed
|
||||
|
||||
Do not move the primary design ownership to the control-plane repository.
|
||||
5
docs/plans/README.md
Normal file
5
docs/plans/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Plans
|
||||
|
||||
This directory stores service-local design notes and implementation plans for `console.svc.plus`.
|
||||
|
||||
The source of truth stays in this upstream repository. Control-plane repositories may reference these documents through git submodule updates after upstream changes are pushed.
|
||||
@ -3,146 +3,142 @@
|
||||
## Scope
|
||||
|
||||
- Runtime: `console.svc.plus`
|
||||
- Frontend host: Vercel
|
||||
- Edge: Cloudflare
|
||||
- Auth backend: `https://accounts.svc.plus`
|
||||
- Topology: `Caddy + Docker Compose + GitHub Actions`
|
||||
- Deploy host: `root@47.120.61.35`
|
||||
- Public domains:
|
||||
- `https://cn.svc.plus`
|
||||
- `https://cn.onwalk.net`
|
||||
- Primary origin: `https://cn.svc.plus`
|
||||
|
||||
This runbook is the minimum checklist for production incidents where login or MFA stops working and browser devtools show `/api/auth/login` or `/api/auth/mfa/*` failures.
|
||||
## Current Delivery Model
|
||||
|
||||
## Expected Request Flow
|
||||
The production frontend is deployed as a prebuilt container image from GitHub Actions.
|
||||
|
||||
1. Browser loads `https://console.svc.plus/login`
|
||||
2. Browser calls same-origin Next routes on `console.svc.plus`
|
||||
3. Next route proxies server-side to `https://accounts.svc.plus/api/auth/*`
|
||||
4. `accounts.svc.plus` returns either a session token or an MFA challenge
|
||||
- The target host does not build images locally.
|
||||
- The workflow builds an `linux/amd64` image and pushes it to `ghcr.io/<owner>/dashboard:<sha>`.
|
||||
- The host only performs `docker login`, `docker compose pull`, static asset extraction, and `docker compose up`.
|
||||
- `knowledge/` is cloned during CI build and packed into the image.
|
||||
- Static assets are extracted from the image into a shared Docker volume so Caddy can serve `/_next/static/*` and checked-in public files directly.
|
||||
|
||||
The browser should not call `accounts.svc.plus` directly for login.
|
||||
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.
|
||||
|
||||
## Fast Triage
|
||||
## Runtime Layout
|
||||
|
||||
Run these checks first:
|
||||
Remote directory:
|
||||
|
||||
```bash
|
||||
curl -si https://console.svc.plus/login | sed -n '1,20p'
|
||||
curl -si https://console.svc.plus/api/auth/login | sed -n '1,20p'
|
||||
curl -si https://accounts.svc.plus/healthz | sed -n '1,20p'
|
||||
curl -si https://accounts.svc.plus/api/auth/login | sed -n '1,20p'
|
||||
/opt/console-svc-plus
|
||||
```
|
||||
|
||||
Interpretation:
|
||||
Files deployed there:
|
||||
|
||||
- `console.svc.plus` returns `403` with `cf-mitigated: challenge`
|
||||
Cloudflare is blocking the page or auth API before Vercel sees it.
|
||||
- `console.svc.plus/api/auth/login` returns `404`
|
||||
Vercel production is not serving the expected Next route, or Cloudflare is pointing at the wrong origin/deployment behavior.
|
||||
- `accounts.svc.plus/healthz` fails
|
||||
Back-end outage. Fix backend first.
|
||||
- `accounts.svc.plus/api/auth/login` returns `200` with `mfaRequired`
|
||||
Backend is healthy; continue on console/Vercel/Cloudflare.
|
||||
```bash
|
||||
docker-compose.yml
|
||||
Caddyfile
|
||||
.env.runtime
|
||||
```
|
||||
|
||||
## Application Checks
|
||||
Containers:
|
||||
|
||||
Verify the current build still contains the auth routes:
|
||||
- `dashboard`: Next.js standalone runtime on port `3000`
|
||||
- `frontend-assets`: one-shot task that copies `static/` and `public/` into a shared volume
|
||||
- `caddy`: TLS termination and reverse proxy
|
||||
|
||||
## GitHub Actions Inputs
|
||||
|
||||
Workflow:
|
||||
|
||||
```text
|
||||
.github/workflows/service_release_frontend-deploy.yml
|
||||
```
|
||||
|
||||
Secrets required:
|
||||
|
||||
- `FRONTEND_DEPLOY_SSH_KEY`
|
||||
- `OPENCLAW_GATEWAY_TOKEN` if used
|
||||
- `VAULT_TOKEN` if used
|
||||
- `AI_GATEWAY_ACCESS_TOKEN` if used
|
||||
- `INTERNAL_SERVICE_TOKEN` if used
|
||||
- `CLOUDFLARE_API_TOKEN` if used
|
||||
|
||||
Repository/environment variables recommended:
|
||||
|
||||
- `APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_SITE_URL`
|
||||
- `NEXT_PUBLIC_LOGIN_URL`
|
||||
- `NEXT_PUBLIC_DOCS_BASE_URL`
|
||||
- `ACCOUNT_SERVICE_URL`
|
||||
- `NEXT_PUBLIC_ACCOUNT_SERVICE_URL`
|
||||
- `SERVER_SERVICE_URL`
|
||||
- `NEXT_PUBLIC_SERVER_SERVICE_URL`
|
||||
- `RUNTIME_HOSTNAME`
|
||||
- `DEPLOYMENT_HOSTNAME`
|
||||
- `NEXT_PUBLIC_RUNTIME_ENVIRONMENT`
|
||||
- `NEXT_PUBLIC_RUNTIME_REGION`
|
||||
- `NEXT_PUBLIC_GISCUS_*`
|
||||
- `NEXT_PUBLIC_STRIPE_*`
|
||||
- `NEXT_PUBLIC_PAYPAL_CLIENT_ID`
|
||||
|
||||
## 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 renders `.env.runtime`.
|
||||
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
|
||||
|
||||
Local syntax checks:
|
||||
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus
|
||||
yarn build
|
||||
cat .next/app-path-routes-manifest.json | jq 'with_entries(select(.key|test("/api/auth/")))'
|
||||
bash -n scripts/github-actions/render-frontend-runtime-env.sh
|
||||
bash -n scripts/github-actions/deploy-frontend-single-node.sh
|
||||
cp deploy/single-node/.env.runtime.example deploy/single-node/.env.runtime
|
||||
docker compose -f deploy/single-node/docker-compose.yml --env-file deploy/single-node/.env.runtime config >/tmp/console-compose.rendered.yaml
|
||||
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())
|
||||
print('workflow yaml ok')
|
||||
PY
|
||||
```
|
||||
|
||||
Verify the login page still uses same-origin routes:
|
||||
Remote checks:
|
||||
|
||||
```bash
|
||||
nl -ba 'src/app/(auth)/login/LoginForm.tsx' | sed -n '64,180p'
|
||||
nl -ba 'src/app/api/auth/login/route.ts' | sed -n '1,180p'
|
||||
nl -ba 'src/app/api/auth/mfa/verify/route.ts' | sed -n '1,180p'
|
||||
ssh root@47.120.61.35 "cd /opt/console-svc-plus && docker compose --env-file .env.runtime ps"
|
||||
ssh root@47.120.61.35 "curl -fsSI -H 'Host: cn.svc.plus' http://127.0.0.1/"
|
||||
curl -fsSIL https://cn.svc.plus
|
||||
curl -fsSIL https://cn.onwalk.net
|
||||
```
|
||||
|
||||
Expected behavior:
|
||||
## Failure Signatures
|
||||
|
||||
- `LoginForm` posts to `/api/auth/login`
|
||||
- login proxy accepts backend `mfaRequired` / `mfaTicket`
|
||||
- MFA verify proxy calls `/api/auth/mfa/verify`
|
||||
- `docker login ghcr.io` fails
|
||||
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.svc.plus` returns `502`
|
||||
Caddy is up, but the `dashboard` container failed or is not reachable on port `3000`.
|
||||
- `cn.onwalk.net` does not redirect
|
||||
Check the deployed `Caddyfile` and domain DNS.
|
||||
|
||||
## Vercel Checks
|
||||
## Rollback
|
||||
|
||||
In the Vercel project for `console-svc-plus`, verify:
|
||||
|
||||
1. The production deployment corresponds to the intended git commit.
|
||||
2. Framework preset is `Next.js`.
|
||||
3. Build command is `yarn build` or the project default, not a static export command.
|
||||
4. Output is not being overridden to static export.
|
||||
5. Production Functions include `app/api/auth/login` and the other `app/api/auth/*` handlers.
|
||||
6. Required runtime env vars are present for the auth proxy path if they are managed in Vercel.
|
||||
|
||||
If the route exists locally but Vercel returns `404`, suspect:
|
||||
|
||||
- wrong production deployment selected
|
||||
- wrong root directory/project link
|
||||
- stale alias or domain assignment
|
||||
- build output mismatch between local and Vercel
|
||||
|
||||
## Cloudflare Checks
|
||||
|
||||
If `curl` shows `cf-mitigated: challenge`, check Cloudflare first.
|
||||
|
||||
Look for:
|
||||
|
||||
1. Managed Challenge or WAF custom rules affecting `/login`
|
||||
2. Managed Challenge or WAF custom rules affecting `/api/auth/*`
|
||||
3. Bot Fight Mode or Super Bot Fight Mode interactions
|
||||
4. Transform/redirect/cache rules that alter `/api/auth/*`
|
||||
5. Page Rules or Ruleset Engine policies applied only to the production hostname
|
||||
|
||||
Recommended policy for auth API:
|
||||
|
||||
- Do not cache `/api/auth/*`
|
||||
- Do not apply JS challenge to `/api/auth/*`
|
||||
- Keep standard security headers, but let requests reach Vercel
|
||||
|
||||
## Backend Verification
|
||||
|
||||
Use the backend directly to prove whether auth is healthy:
|
||||
1. Re-run the workflow with a previous known-good image tag.
|
||||
2. Or update `/opt/console-svc-plus/.env.runtime` and set `FRONTEND_IMAGE=ghcr.io/<owner>/dashboard:<previous-tag>`.
|
||||
3. Restart the services:
|
||||
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/accounts.svc.plus
|
||||
set -a; source .env; set +a
|
||||
payload=$(printf '{"identifier":"admin@svc.plus","password":"%s"}' "$SUPERADMIN_PASSWORD")
|
||||
curl -sS -X POST https://accounts.svc.plus/api/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$payload"
|
||||
ssh root@47.120.61.35 "cd /opt/console-svc-plus && docker compose --env-file .env.runtime run --rm frontend-assets"
|
||||
ssh root@47.120.61.35 "cd /opt/console-svc-plus && docker compose --env-file .env.runtime up -d dashboard caddy"
|
||||
```
|
||||
|
||||
Expected for an MFA-enabled admin:
|
||||
|
||||
- HTTP `200`
|
||||
- response contains `mfaRequired`
|
||||
- response contains `mfaTicket` or `mfaToken`
|
||||
|
||||
## Known Failure Signatures
|
||||
|
||||
- `POST https://console.svc.plus/api/auth/login 404`
|
||||
Likely Vercel deployment mismatch or route not published.
|
||||
- `403` with `cf-mitigated: challenge`
|
||||
Cloudflare blocked request before Vercel.
|
||||
- login returns generic failure even though backend returns MFA challenge
|
||||
Console auth proxy is not parsing MFA fields correctly.
|
||||
- MFA code accepted by authenticator but web login still fails
|
||||
Console proxy may be calling the setup endpoint instead of the login MFA endpoint.
|
||||
|
||||
## Rollback Strategy
|
||||
|
||||
When a release breaks auth:
|
||||
|
||||
1. Remove or relax Cloudflare rules affecting `/login` and `/api/auth/*`
|
||||
2. Re-point domain to last known-good Vercel production deployment
|
||||
3. Roll back `console.svc.plus`
|
||||
4. Only then consider `accounts.svc.plus` rollback
|
||||
|
||||
## Related Files
|
||||
|
||||
- `src/app/(auth)/login/LoginForm.tsx`
|
||||
- `src/app/api/auth/login/route.ts`
|
||||
- `src/app/api/auth/mfa/status/route.ts`
|
||||
- `src/app/api/auth/mfa/verify/route.ts`
|
||||
- `src/server/serviceConfig.ts`
|
||||
4. Verify `https://cn.svc.plus` again before closing the incident.
|
||||
|
||||
@ -1,31 +1,28 @@
|
||||
# 部署
|
||||
|
||||
该仓库以 Web 前端体验为主,文档需要覆盖产品流程、界面边界与集成触点。
|
||||
## 生产基线
|
||||
|
||||
本页用于统一部署前提、支持的拓扑、运维检查项与回滚注意事项。
|
||||
- 运行拓扑: `Caddy + Docker Compose`
|
||||
- 目标主机: `47.120.61.35`
|
||||
- 域名:
|
||||
- `cn.svc.plus`
|
||||
- `cn.onwalk.net`
|
||||
- 前端独立发布流水线: `.github/workflows/service_release_frontend-deploy.yml`
|
||||
|
||||
## 与当前代码对齐的说明
|
||||
## 运行方式
|
||||
|
||||
- 文档目标仓库: `console.svc.plus`
|
||||
- 仓库类型: `frontend`
|
||||
- 构建与运行依据: package.json (`dashboard`)
|
||||
- 主要实现与运维目录: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- `package.json` 脚本快照: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
|
||||
前端镜像在 GitHub Actions 中完成构建并推送到镜像仓库,目标主机只负责拉取镜像和启动容器,不在机器上本地构建。
|
||||
|
||||
## 需要继续归并的现有文档
|
||||
当前方案尽量以静态模式运行:
|
||||
|
||||
- Caddy 直接服务 `/_next/static/*` 与 `public/` 里的静态资源。
|
||||
- Next.js standalone 容器只承接动态页面、认证接口和代理接口。
|
||||
- `knowledge/` 在 CI 阶段拉取,并在 Docker 打包时直接写入镜像。
|
||||
|
||||
这是针对当前单机弱 IO 环境的权衡。后续如果 `docs.svc.plus` 被拆成独立 API 服务,需要同步调整这里和 `docs/usage/deployment.md` 的镜像内容与路由职责。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `development/dev-setup.md`
|
||||
- `getting-started/installation.md`
|
||||
- `getting-started/quickstart.md`
|
||||
- `governance/release-process.md`
|
||||
- `operations/runbooks/README.md`
|
||||
- `operations/runbooks/rag-server.md`
|
||||
- `usage/deployment.md`
|
||||
- `zh/development/dev-setup.md`
|
||||
|
||||
## 本页下一步应补充的内容
|
||||
|
||||
- 先描述当前已落地实现,再补充未来规划,避免只写愿景不写现状。
|
||||
- 术语需要与仓库根 README、构建清单和实际目录保持一致。
|
||||
- 将上方列出的历史 runbook、spec、子系统说明逐步链接并归并到本页。
|
||||
- 每次发布前,依据当前脚本、清单、CI/CD 流程和环境契约重新核对部署步骤。
|
||||
- `governance/release-process.md`
|
||||
- `development/dev-setup.md`
|
||||
|
||||
88
scripts/github-actions/deploy-frontend-single-node.sh
Executable file
88
scripts/github-actions/deploy-frontend-single-node.sh
Executable file
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
DEPLOY_SOURCE_DIR="${REPO_ROOT}/deploy/single-node"
|
||||
|
||||
require_env() {
|
||||
local key="$1"
|
||||
local value="${!key-}"
|
||||
if [[ -z "${value}" ]]; then
|
||||
echo "Missing required environment variable: ${key}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_env DEPLOY_HOST
|
||||
require_env DEPLOY_USER
|
||||
require_env DEPLOY_DIR
|
||||
require_env SSH_PRIVATE_KEY
|
||||
require_env GHCR_USERNAME
|
||||
require_env GHCR_PASSWORD
|
||||
require_env FRONTEND_IMAGE
|
||||
require_env PRIMARY_DOMAIN
|
||||
require_env SECONDARY_DOMAIN
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "${WORK_DIR}"' EXIT
|
||||
|
||||
RUNTIME_ENV_FILE="${WORK_DIR}/.env.runtime"
|
||||
RELEASE_ARCHIVE="${WORK_DIR}/console-svc-plus-release.tgz"
|
||||
REMOTE_ARCHIVE="/tmp/console-svc-plus-release-${GITHUB_SHA:-manual}.tgz"
|
||||
SSH_KEY_FILE="${WORK_DIR}/deploy.key"
|
||||
KNOWN_HOSTS_FILE="${WORK_DIR}/known_hosts"
|
||||
|
||||
bash "${SCRIPT_DIR}/render-frontend-runtime-env.sh" "${RUNTIME_ENV_FILE}"
|
||||
|
||||
cp "${DEPLOY_SOURCE_DIR}/docker-compose.yml" "${WORK_DIR}/docker-compose.yml"
|
||||
cp "${DEPLOY_SOURCE_DIR}/Caddyfile" "${WORK_DIR}/Caddyfile"
|
||||
|
||||
tar -C "${WORK_DIR}" -czf "${RELEASE_ARCHIVE}" \
|
||||
docker-compose.yml \
|
||||
Caddyfile \
|
||||
.env.runtime
|
||||
|
||||
printf '%s\n' "${SSH_PRIVATE_KEY}" > "${SSH_KEY_FILE}"
|
||||
chmod 600 "${SSH_KEY_FILE}"
|
||||
ssh-keyscan -H "${DEPLOY_HOST}" > "${KNOWN_HOSTS_FILE}"
|
||||
|
||||
SSH_BASE=(
|
||||
ssh
|
||||
-i "${SSH_KEY_FILE}"
|
||||
-o StrictHostKeyChecking=yes
|
||||
-o UserKnownHostsFile="${KNOWN_HOSTS_FILE}"
|
||||
"${DEPLOY_USER}@${DEPLOY_HOST}"
|
||||
)
|
||||
|
||||
SCP_BASE=(
|
||||
scp
|
||||
-i "${SSH_KEY_FILE}"
|
||||
-o StrictHostKeyChecking=yes
|
||||
-o UserKnownHostsFile="${KNOWN_HOSTS_FILE}"
|
||||
)
|
||||
|
||||
printf '%s' "${GHCR_PASSWORD}" | "${SSH_BASE[@]}" "docker login ghcr.io -u '${GHCR_USERNAME}' --password-stdin"
|
||||
|
||||
"${SCP_BASE[@]}" "${RELEASE_ARCHIVE}" "${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_ARCHIVE}"
|
||||
|
||||
"${SSH_BASE[@]}" \
|
||||
"DEPLOY_DIR='${DEPLOY_DIR}' REMOTE_ARCHIVE='${REMOTE_ARCHIVE}' PROJECT_NAME='console-svc-plus' bash -s" <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "${tmp_dir}" "${REMOTE_ARCHIVE}"' EXIT
|
||||
|
||||
mkdir -p "${DEPLOY_DIR}"
|
||||
tar -xzf "${REMOTE_ARCHIVE}" -C "${tmp_dir}"
|
||||
|
||||
install -m 0644 "${tmp_dir}/docker-compose.yml" "${DEPLOY_DIR}/docker-compose.yml"
|
||||
install -m 0644 "${tmp_dir}/Caddyfile" "${DEPLOY_DIR}/Caddyfile"
|
||||
install -m 0600 "${tmp_dir}/.env.runtime" "${DEPLOY_DIR}/.env.runtime"
|
||||
|
||||
cd "${DEPLOY_DIR}"
|
||||
docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime pull dashboard caddy
|
||||
docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime run --rm frontend-assets
|
||||
docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime up -d --remove-orphans dashboard caddy
|
||||
docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime ps
|
||||
EOF
|
||||
76
scripts/github-actions/render-frontend-runtime-env.sh
Executable file
76
scripts/github-actions/render-frontend-runtime-env.sh
Executable file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTPUT_PATH="${1:?usage: render-frontend-runtime-env.sh <output-path>}"
|
||||
|
||||
mkdir -p "$(dirname "${OUTPUT_PATH}")"
|
||||
: > "${OUTPUT_PATH}"
|
||||
|
||||
append_env() {
|
||||
local key="$1"
|
||||
local value="${2-}"
|
||||
printf '%s=%s\n' "${key}" "${value}" >> "${OUTPUT_PATH}"
|
||||
}
|
||||
|
||||
require_env() {
|
||||
local key="$1"
|
||||
local value="${!key-}"
|
||||
if [[ -z "${value}" ]]; then
|
||||
echo "Missing required environment variable: ${key}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_env FRONTEND_IMAGE
|
||||
require_env PRIMARY_DOMAIN
|
||||
require_env SECONDARY_DOMAIN
|
||||
|
||||
append_env FRONTEND_IMAGE "${FRONTEND_IMAGE}"
|
||||
append_env PRIMARY_DOMAIN "${PRIMARY_DOMAIN}"
|
||||
append_env SECONDARY_DOMAIN "${SECONDARY_DOMAIN}"
|
||||
|
||||
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 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 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}"
|
||||
append_env NEXT_PUBLIC_ACCOUNT_SERVICE_URL "${NEXT_PUBLIC_ACCOUNT_SERVICE_URL:-${ACCOUNT_SERVICE_URL:-https://accounts.svc.plus}}"
|
||||
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 OPENCLAW_GATEWAY_REMOTE_URL "${OPENCLAW_GATEWAY_REMOTE_URL-}"
|
||||
append_env OPENCLAW_GATEWAY_TOKEN "${OPENCLAW_GATEWAY_TOKEN-}"
|
||||
append_env VAULT_SERVER_URL "${VAULT_SERVER_URL-}"
|
||||
append_env VAULT_NAMESPACE "${VAULT_NAMESPACE-}"
|
||||
append_env VAULT_TOKEN "${VAULT_TOKEN-}"
|
||||
append_env APISIX_AI_GATEWAY_URL "${APISIX_AI_GATEWAY_URL-}"
|
||||
append_env AI_GATEWAY_ACCESS_TOKEN "${AI_GATEWAY_ACCESS_TOKEN-}"
|
||||
append_env INTERNAL_SERVICE_TOKEN "${INTERNAL_SERVICE_TOKEN-}"
|
||||
append_env CLOUDFLARE_API_TOKEN "${CLOUDFLARE_API_TOKEN-}"
|
||||
append_env CLOUDFLARE_ACCOUNT_ID "${CLOUDFLARE_ACCOUNT_ID-}"
|
||||
append_env CLOUDFLARE_WEB_ANALYTICS_SITE_TAG "${CLOUDFLARE_WEB_ANALYTICS_SITE_TAG-}"
|
||||
append_env CLOUDFLARE_ZONE_TAG "${CLOUDFLARE_ZONE_TAG-}"
|
||||
append_env ROOT_EMAIL_WHITELIST "${ROOT_EMAIL_WHITELIST:-admin@svc.plus}"
|
||||
append_env NEXT_PUBLIC_PAYPAL_CLIENT_ID "${NEXT_PUBLIC_PAYPAL_CLIENT_ID-}"
|
||||
append_env NEXT_PUBLIC_GISCUS_REPO "${NEXT_PUBLIC_GISCUS_REPO:-cloud-neutral-toolkit/console.svc.plus}"
|
||||
append_env NEXT_PUBLIC_GISCUS_REPO_ID "${NEXT_PUBLIC_GISCUS_REPO_ID-}"
|
||||
append_env NEXT_PUBLIC_GISCUS_CATEGORY "${NEXT_PUBLIC_GISCUS_CATEGORY:-General}"
|
||||
append_env NEXT_PUBLIC_GISCUS_CATEGORY_ID "${NEXT_PUBLIC_GISCUS_CATEGORY_ID-}"
|
||||
append_env NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO "${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO-}"
|
||||
append_env NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION "${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION-}"
|
||||
append_env NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO "${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO-}"
|
||||
append_env NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION "${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION-}"
|
||||
append_env NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO "${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO-}"
|
||||
append_env NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION "${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION-}"
|
||||
Loading…
Reference in New Issue
Block a user