fix: ci lint
@ -0,0 +1,70 @@
|
||||
name: Helm CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
tags:
|
||||
- "v*"
|
||||
paths:
|
||||
- ".github/workflows/helm-ci.yml"
|
||||
- ".github/workflows/helm.yml"
|
||||
- "Cargo.toml"
|
||||
- "helm/**"
|
||||
- "docs/src/content/docs/**/deployment/helm.mdx"
|
||||
- "docs/src/content/docs/deployment/helm.mdx"
|
||||
pull_request:
|
||||
branches: ['**']
|
||||
paths:
|
||||
- ".github/workflows/helm-ci.yml"
|
||||
- ".github/workflows/helm.yml"
|
||||
- "Cargo.toml"
|
||||
- "helm/**"
|
||||
- "docs/src/content/docs/**/deployment/helm.mdx"
|
||||
- "docs/src/content/docs/deployment/helm.mdx"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Check chart and Cargo versions
|
||||
shell: bash
|
||||
run: |
|
||||
chart_version="$(sed -n 's/^version:[[:space:]]*//p' helm/synctv/Chart.yaml | head -n1 | tr -d '"')"
|
||||
app_version="$(sed -n 's/^appVersion:[[:space:]]*//p' helm/synctv/Chart.yaml | head -n1 | tr -d '"')"
|
||||
cargo_version="$(awk '/^\[workspace.package\]/{in_section=1; next} /^\[/{in_section=0} in_section && $1 == "version" {gsub(/"/, "", $3); print $3; exit}' Cargo.toml)"
|
||||
|
||||
if [ "$chart_version" != "$cargo_version" ]; then
|
||||
echo "Chart version ($chart_version) must match Cargo workspace version ($cargo_version)." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$app_version" != "$cargo_version" ]; then
|
||||
echo "Chart appVersion ($app_version) must match Cargo workspace version ($cargo_version)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Lint chart
|
||||
run: helm lint ./helm/synctv
|
||||
|
||||
- name: Render default manifests
|
||||
run: helm template synctv ./helm/synctv --namespace synctv >/tmp/synctv-default.yaml
|
||||
|
||||
- name: Render gRPC ingress manifests
|
||||
run: helm template synctv ./helm/synctv --namespace synctv --set ingress.grpc.enabled=true >/tmp/synctv-grpc.yaml
|
||||
|
||||
- name: Render KubeBlocks manifests
|
||||
run: helm template synctv ./helm/synctv --namespace synctv --set postgresql.mode=kubeblocks --set redis.mode=kubeblocks >/tmp/synctv-kubeblocks.yaml
|
||||
@ -0,0 +1,228 @@
|
||||
name: Helm Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release-tag:
|
||||
description: "Release tag, for example v0.2.0"
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
token:
|
||||
required: true
|
||||
HELM_OCI_REPOSITORY:
|
||||
required: false
|
||||
HELM_REPOSITORY_URL:
|
||||
required: false
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_REPOSITORY:
|
||||
required: false
|
||||
DOCKERHUB_PASSWORD:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name "$GITHUB_ACTOR"
|
||||
git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Read chart metadata
|
||||
id: chart
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.release-tag }}
|
||||
GHCR_REPOSITORY: ${{ github.repository }}
|
||||
HELM_OCI_REPOSITORY: ${{ vars.HELM_OCI_REPOSITORY || secrets.HELM_OCI_REPOSITORY }}
|
||||
HELM_REPOSITORY_BRANCH: ${{ vars.HELM_REPOSITORY_BRANCH || 'helm-charts' }}
|
||||
HELM_REPOSITORY_URL: ${{ vars.HELM_REPOSITORY_URL || secrets.HELM_REPOSITORY_URL }}
|
||||
DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME || secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_REPOSITORY: ${{ vars.DOCKERHUB_REPOSITORY || secrets.DOCKERHUB_REPOSITORY }}
|
||||
run: |
|
||||
chart_name="$(sed -n 's/^name:[[:space:]]*//p' helm/synctv/Chart.yaml | head -n1 | tr -d '"')"
|
||||
chart_version="$(sed -n 's/^version:[[:space:]]*//p' helm/synctv/Chart.yaml | head -n1 | tr -d '"')"
|
||||
app_version="$(sed -n 's/^appVersion:[[:space:]]*//p' helm/synctv/Chart.yaml | head -n1 | tr -d '"')"
|
||||
cargo_version="$(awk '/^\[workspace.package\]/{in_section=1; next} /^\[/{in_section=0} in_section && $1 == "version" {gsub(/"/, "", $3); print $3; exit}' Cargo.toml)"
|
||||
|
||||
if [ -z "$chart_name" ] || [ -z "$chart_version" ] || [ -z "$app_version" ] || [ -z "$cargo_version" ]; then
|
||||
echo "Chart.yaml must define name, version, and appVersion; Cargo.toml must define workspace.package.version." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag_version="${RELEASE_TAG#v}"
|
||||
if [ "$chart_version" != "$tag_version" ]; then
|
||||
echo "Chart version ($chart_version) must match release tag ($tag_version)." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$app_version" != "$tag_version" ]; then
|
||||
echo "Chart appVersion ($app_version) must match release tag ($tag_version)." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$cargo_version" != "$tag_version" ]; then
|
||||
echo "Cargo workspace version ($cargo_version) must match release tag ($tag_version)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
owner="$(printf '%s' "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')"
|
||||
repo="$(printf '%s' "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [ -n "$DOCKERHUB_REPOSITORY" ]; then
|
||||
case "$DOCKERHUB_REPOSITORY" in
|
||||
*/*)
|
||||
dockerhub_repository="$DOCKERHUB_REPOSITORY"
|
||||
;;
|
||||
*)
|
||||
dockerhub_repository="$DOCKERHUB_USERNAME/$DOCKERHUB_REPOSITORY"
|
||||
;;
|
||||
esac
|
||||
elif [ -n "$DOCKERHUB_USERNAME" ]; then
|
||||
dockerhub_repository="$DOCKERHUB_USERNAME/$repo"
|
||||
fi
|
||||
|
||||
if [ -n "$HELM_REPOSITORY_URL" ]; then
|
||||
pages_url="$HELM_REPOSITORY_URL"
|
||||
else
|
||||
pages_url="https://$owner.github.io/$repo"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "name=$chart_name"
|
||||
echo "version=$chart_version"
|
||||
echo "app_version=$app_version"
|
||||
echo "pages_url=$pages_url"
|
||||
echo "chart_branch=$HELM_REPOSITORY_BRANCH"
|
||||
echo "dockerhub_username=$DOCKERHUB_USERNAME"
|
||||
echo 'oci_repos<<EOF'
|
||||
if [ -n "$HELM_OCI_REPOSITORY" ]; then
|
||||
printf '%s\n' "$HELM_OCI_REPOSITORY"
|
||||
else
|
||||
echo "ghcr.io/$GHCR_REPOSITORY/charts"
|
||||
if [ -n "${dockerhub_repository:-}" ]; then
|
||||
echo "docker.io/$dockerhub_repository/charts"
|
||||
fi
|
||||
fi
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate chart
|
||||
run: |
|
||||
helm lint ./helm/synctv
|
||||
helm template synctv ./helm/synctv --namespace synctv >/tmp/synctv-default.yaml
|
||||
helm template synctv ./helm/synctv --namespace synctv --set ingress.grpc.enabled=true >/tmp/synctv-grpc.yaml
|
||||
helm template synctv ./helm/synctv --namespace synctv --set postgresql.mode=kubeblocks --set redis.mode=kubeblocks >/tmp/synctv-kubeblocks.yaml
|
||||
|
||||
- name: Package chart
|
||||
run: |
|
||||
mkdir -p dist
|
||||
helm package ./helm/synctv --destination dist
|
||||
|
||||
- name: Login to GHCR
|
||||
run: echo "${{ secrets.token }}" | helm registry login ghcr.io --username "$GITHUB_ACTOR" --password-stdin
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: steps.chart.outputs.dockerhub_username != ''
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ steps.chart.outputs.dockerhub_username }}
|
||||
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
run: echo "$DOCKERHUB_PASSWORD" | helm registry login docker.io --username "$DOCKERHUB_USERNAME" --password-stdin
|
||||
|
||||
- name: Push chart to OCI registries
|
||||
shell: bash
|
||||
run: |
|
||||
while IFS= read -r oci_repo; do
|
||||
if [ -z "$oci_repo" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
check_dir="$(mktemp -d)"
|
||||
if helm pull "oci://$oci_repo/${{ steps.chart.outputs.name }}" \
|
||||
--version "${{ steps.chart.outputs.version }}" \
|
||||
--destination "$check_dir" >/dev/null 2>&1; then
|
||||
echo "OCI chart $oci_repo/${{ steps.chart.outputs.name }}:${{ steps.chart.outputs.version }} already exists; skipping push."
|
||||
continue
|
||||
fi
|
||||
|
||||
helm push "dist/${{ steps.chart.outputs.name }}-${{ steps.chart.outputs.version }}.tgz" \
|
||||
"oci://$oci_repo"
|
||||
done <<'EOF'
|
||||
${{ steps.chart.outputs.oci_repos }}
|
||||
EOF
|
||||
|
||||
- name: Open chart repository update PR
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.token }}
|
||||
run: |
|
||||
chart_branch="${{ steps.chart.outputs.chart_branch }}"
|
||||
update_branch="automation/helm-charts-${{ steps.chart.outputs.version }}"
|
||||
chart_repo_dir="$(mktemp -d)"
|
||||
changed_file="$RUNNER_TEMP/helm-chart-repo-changed"
|
||||
|
||||
if ! git ls-remote --exit-code origin "refs/heads/$chart_branch" >/dev/null 2>&1; then
|
||||
echo "Branch '$chart_branch' does not exist; creating it."
|
||||
init_dir="$(mktemp -d)"
|
||||
git clone --no-checkout "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$init_dir"
|
||||
(
|
||||
cd "$init_dir"
|
||||
git switch --orphan "$chart_branch"
|
||||
git rm -rf . >/dev/null 2>&1 || true
|
||||
printf "# SyncTV Helm Charts\n\nThis branch is maintained by the release workflow.\n" > README.md
|
||||
git add README.md
|
||||
git commit -m "helm: initialize chart repository"
|
||||
git push origin "$chart_branch"
|
||||
)
|
||||
fi
|
||||
|
||||
git clone --branch "$chart_branch" --single-branch "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$chart_repo_dir"
|
||||
cp "dist/${{ steps.chart.outputs.name }}-${{ steps.chart.outputs.version }}.tgz" "$chart_repo_dir/"
|
||||
|
||||
(
|
||||
cd "$chart_repo_dir"
|
||||
helm repo index . --url "${{ steps.chart.outputs.pages_url }}"
|
||||
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "Chart repository is already up to date."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
touch "$changed_file"
|
||||
git checkout -B "$update_branch"
|
||||
git add .
|
||||
git commit -m "helm: publish ${{ steps.chart.outputs.name }} ${{ steps.chart.outputs.version }}"
|
||||
git push --force-with-lease "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$update_branch"
|
||||
)
|
||||
|
||||
if [ ! -f "$changed_file" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if gh pr view "$update_branch" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh pr edit "$update_branch" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "helm: publish ${{ steps.chart.outputs.name }} ${{ steps.chart.outputs.version }}" \
|
||||
--body "Publishes Helm chart ${{ steps.chart.outputs.name }} version ${{ steps.chart.outputs.version }} to the chart repository branch."
|
||||
else
|
||||
gh pr create \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--base "$chart_branch" \
|
||||
--head "$update_branch" \
|
||||
--title "helm: publish ${{ steps.chart.outputs.name }} ${{ steps.chart.outputs.version }}" \
|
||||
--body "Publishes Helm chart ${{ steps.chart.outputs.name }} version ${{ steps.chart.outputs.version }} to the chart repository branch."
|
||||
fi
|
||||
@ -0,0 +1,63 @@
|
||||
name: Prepare Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Release version, for example 0.2.0"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ inputs.version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Open Release PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Normalize release version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
version="${{ inputs.version }}"
|
||||
version="${version#v}"
|
||||
echo "value=$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Synchronize release version
|
||||
run: ./scripts/set-release-version.sh "${{ steps.version.outputs.value }}"
|
||||
|
||||
- name: Validate Helm chart
|
||||
run: |
|
||||
helm lint ./helm/synctv
|
||||
helm template synctv ./helm/synctv --namespace synctv >/tmp/synctv-default.yaml
|
||||
helm template synctv ./helm/synctv --namespace synctv --set ingress.grpc.enabled=true >/tmp/synctv-grpc.yaml
|
||||
helm template synctv ./helm/synctv --namespace synctv --set postgresql.mode=kubeblocks --set redis.mode=kubeblocks >/tmp/synctv-kubeblocks.yaml
|
||||
|
||||
- name: Create release preparation PR
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: "release: prepare v${{ steps.version.outputs.value }}"
|
||||
branch: "automation/release-v${{ steps.version.outputs.value }}"
|
||||
delete-branch: true
|
||||
title: "release: prepare v${{ steps.version.outputs.value }}"
|
||||
body: |
|
||||
Prepares release v${{ steps.version.outputs.value }}.
|
||||
|
||||
This updates:
|
||||
- Cargo workspace version and Cargo.lock
|
||||
- Helm chart version and appVersion
|
||||
@ -1,29 +1,26 @@
|
||||
import { execFile } from 'node:child_process';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import { mkdir, readdir } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.resolve(scriptDir, '..', '..');
|
||||
const mmdc = path.join(root, 'node_modules', '.bin', process.platform === 'win32' ? 'mmdc.cmd' : 'mmdc');
|
||||
const sourceDir = path.join(root, 'src', 'diagrams');
|
||||
const outputDir = path.join(root, 'src', 'assets', 'diagrams');
|
||||
const lightConfig = path.join(root, 'scripts', 'mermaid-light-config.json');
|
||||
const darkConfig = path.join(root, 'scripts', 'mermaid-dark-config.json');
|
||||
const puppeteerConfig = path.join(root, 'scripts', 'mermaid-puppeteer-config.json');
|
||||
|
||||
const diagrams = [
|
||||
'architecture',
|
||||
'security-auth-boundary',
|
||||
'production-minimal',
|
||||
'kubernetes-topology',
|
||||
'cluster-runtime',
|
||||
'livestream-pipeline',
|
||||
];
|
||||
const lightConfig = path.join(scriptDir, 'mermaid-light-config.json');
|
||||
const darkConfig = path.join(scriptDir, 'mermaid-dark-config.json');
|
||||
const puppeteerConfig = path.join(scriptDir, 'mermaid-puppeteer-config.json');
|
||||
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const diagrams = (await readdir(sourceDir, { withFileTypes: true }))
|
||||
.filter((entry) => entry.isFile() && entry.name.endsWith('.mmd'))
|
||||
.map((entry) => path.basename(entry.name, '.mmd'))
|
||||
.sort((left, right) => left.localeCompare(right));
|
||||
|
||||
for (const diagram of diagrams) {
|
||||
for (const [theme, config] of [
|
||||
['light', lightConfig],
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Release Process
|
||||
description: How to prepare and publish SyncTV releases across Cargo, images, and Helm charts.
|
||||
---
|
||||
|
||||
SyncTV uses one application version across the Rust workspace, container image, and Helm chart.
|
||||
|
||||
## Prepare a Release
|
||||
|
||||
Open a release preparation PR from GitHub Actions:
|
||||
|
||||
1. Run the `Prepare Release` workflow.
|
||||
2. Enter the next version, for example `0.2.0`.
|
||||
3. Review and merge the generated PR.
|
||||
|
||||
The workflow runs:
|
||||
|
||||
```bash
|
||||
./scripts/set-release-version.sh 0.2.0
|
||||
```
|
||||
|
||||
This synchronizes:
|
||||
|
||||
- `Cargo.toml` `[workspace.package].version`
|
||||
- `Cargo.lock` path package versions
|
||||
- `helm/synctv/Chart.yaml` `version`
|
||||
- `helm/synctv/Chart.yaml` `appVersion`
|
||||
|
||||
## Publish a Release
|
||||
|
||||
After the release preparation PR is merged, create and push a matching tag:
|
||||
|
||||
```bash
|
||||
git tag v0.2.0
|
||||
git push origin v0.2.0
|
||||
```
|
||||
|
||||
The `Release` workflow calls the `Helm Release` workflow, which can only be triggered through `workflow_call`. The Helm workflow verifies that the tag, Cargo workspace version, Helm chart version, and Helm `appVersion` all match.
|
||||
|
||||
The workflow publishes:
|
||||
|
||||
- A GitHub release.
|
||||
- The Helm chart as an OCI artifact. The default parent repository is `ghcr.io/<owner>/<repo>/charts`; for this repository that is `ghcr.io/synctv-org/synctv/charts`.
|
||||
- A PR into the `helm-charts` branch containing the packaged chart and updated `index.yaml`.
|
||||
|
||||
The traditional Helm repository becomes available after the `helm-charts` PR is merged and GitHub Pages serves that branch.
|
||||
|
||||
## Helm Publishing Targets
|
||||
|
||||
Helm OCI target selection follows the Docker workflow style: default to a GHCR path derived from the current GitHub repository; if DockerHub variables are configured, also publish to the matching DockerHub charts path; repository variables or secrets can explicitly override the target.
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `HELM_OCI_REPOSITORY` | Derived automatically | Explicit Helm OCI parent repository override. When set, only this repository is used. Helm appends the chart name when installing. |
|
||||
| `DOCKERHUB_USERNAME` | Empty | Same as the Docker workflow; when set, also publish to `docker.io/<username>/<repo>/charts`. |
|
||||
| `DOCKERHUB_REPOSITORY` | Empty | Same as the Docker workflow; overrides the DockerHub repository name. |
|
||||
| `HELM_REPOSITORY_BRANCH` | `helm-charts` | Traditional Helm repository branch. The workflow initializes it automatically if it does not exist. |
|
||||
| `HELM_REPOSITORY_URL` | `https://<owner>.github.io/<repo>` | Public URL passed to `helm repo index --url`. |
|
||||
@ -0,0 +1,38 @@
|
||||
flowchart TB
|
||||
Client[客户端应用]
|
||||
Edge[TLS 反向代理 / Ingress]
|
||||
CLI[CLI / 管理工具]
|
||||
|
||||
subgraph SyncTV["SyncTV 单二进制"]
|
||||
direction TB
|
||||
HTTP[HTTP REST<br/>OpenAPI / 健康检查 / 代理路由]
|
||||
GRPC[公开 gRPC]
|
||||
WS[WebSocket 实时协作]
|
||||
MGMT[管理 gRPC]
|
||||
Provider[媒体 Provider]
|
||||
Proxy[媒体代理<br/>slice cache]
|
||||
Live[直播<br/>RTMP / STUN / HLS]
|
||||
Cluster[集群协调]
|
||||
end
|
||||
|
||||
PG[(PostgreSQL)]
|
||||
Redis[(Redis)]
|
||||
External[外部媒体服务]
|
||||
HLS[(HLS 后端<br/>本地 / 共享 / OSS)]
|
||||
|
||||
Client --> Edge
|
||||
Edge --> HTTP
|
||||
Edge --> GRPC
|
||||
Edge --> WS
|
||||
CLI --> MGMT
|
||||
|
||||
HTTP --> PG
|
||||
GRPC --> PG
|
||||
MGMT --> PG
|
||||
WS --> Redis
|
||||
Cluster --> Redis
|
||||
Provider --> External
|
||||
Proxy --> External
|
||||
Proxy --> Redis
|
||||
Live --> HLS
|
||||
Live -. 发布节点 HLS 代理 .-> Redis
|
||||
@ -0,0 +1,46 @@
|
||||
flowchart TB
|
||||
Client[客户端应用]
|
||||
|
||||
subgraph Edge["Ingress / 反向代理"]
|
||||
HTTP[HTTP REST / WebSocket]
|
||||
GRPC[公开 gRPC]
|
||||
end
|
||||
|
||||
subgraph Nodes["SyncTV 副本"]
|
||||
A[节点 A<br/>房间 / Provider / 直播]
|
||||
B[节点 B<br/>房间 / Provider / 直播]
|
||||
C[节点 C<br/>房间 / Provider / 直播]
|
||||
end
|
||||
|
||||
PG[(PostgreSQL<br/>持久状态)]
|
||||
Redis[(Redis<br/>协调状态)]
|
||||
HLS[(HLS 后端<br/>memory / file / OSS)]
|
||||
HLSProxy[发布节点<br/>HLS gRPC 代理]
|
||||
|
||||
Client --> HTTP
|
||||
Client --> GRPC
|
||||
HTTP --> A
|
||||
HTTP --> B
|
||||
HTTP --> C
|
||||
GRPC --> A
|
||||
GRPC --> B
|
||||
GRPC --> C
|
||||
|
||||
A --> PG
|
||||
B --> PG
|
||||
C --> PG
|
||||
A <--> Redis
|
||||
B <--> Redis
|
||||
C <--> Redis
|
||||
A --> HLS
|
||||
B --> HLS
|
||||
C --> HLS
|
||||
A <--> HLSProxy
|
||||
B <--> HLSProxy
|
||||
C <--> HLSProxy
|
||||
|
||||
Redis -. 节点发现 .-> A
|
||||
Redis -. pub/sub .-> B
|
||||
Redis -. streams .-> C
|
||||
Redis -. leader lock .-> A
|
||||
Redis -. publisher registry .-> HLSProxy
|
||||
@ -0,0 +1,41 @@
|
||||
flowchart TB
|
||||
Client[客户端应用]
|
||||
|
||||
subgraph Edge["Kubernetes 边界入口"]
|
||||
HTTPIngress[HTTP Ingress]
|
||||
GRPCIngress[gRPC Ingress<br/>backend-protocol: GRPC]
|
||||
HTTPService[HTTP Service]
|
||||
GRPCService[gRPC Service]
|
||||
end
|
||||
|
||||
subgraph Workload["SyncTV 工作负载"]
|
||||
PodA[SyncTV Pod]
|
||||
PodB[SyncTV Pod]
|
||||
PodC[SyncTV Pod]
|
||||
end
|
||||
|
||||
PG[(PostgreSQL)]
|
||||
Redis[(Redis)]
|
||||
HLS[(可选共享 HLS 存储<br/>或发布节点代理)]
|
||||
External[外部媒体 Provider]
|
||||
|
||||
Client --> HTTPIngress --> HTTPService
|
||||
Client --> GRPCIngress --> GRPCService
|
||||
HTTPService --> PodA
|
||||
HTTPService --> PodB
|
||||
HTTPService --> PodC
|
||||
GRPCService --> PodA
|
||||
GRPCService --> PodB
|
||||
GRPCService --> PodC
|
||||
PodA --> PG
|
||||
PodB --> PG
|
||||
PodC --> PG
|
||||
PodA --> Redis
|
||||
PodB --> Redis
|
||||
PodC --> Redis
|
||||
PodA --> HLS
|
||||
PodB --> HLS
|
||||
PodC --> HLS
|
||||
PodA --> External
|
||||
PodB --> External
|
||||
PodC --> External
|
||||
@ -0,0 +1,24 @@
|
||||
flowchart LR
|
||||
Publisher[推流端<br/>OBS / RTMP 客户端]
|
||||
RTMP[RTMP 入口<br/>认证 + 发布注册]
|
||||
Hub[StreamHub<br/>直播包分发]
|
||||
FLV[HTTP-FLV 会话<br/>低延迟]
|
||||
Remux[HLS remuxer<br/>分片 + playlist 状态]
|
||||
Storage[(HLS 存储<br/>memory / file / OSS)]
|
||||
Playlist[HLS playlist 路由]
|
||||
Segment[HLS segment 路由]
|
||||
Viewer[观看客户端]
|
||||
Redis[(Redis<br/>集群 publisher registry)]
|
||||
Remote[远端节点<br/>HLS/FLV 请求]
|
||||
HLSProxy[发布节点<br/>HLS gRPC 代理]
|
||||
|
||||
Publisher --> RTMP --> Hub
|
||||
RTMP --> Redis
|
||||
Hub --> FLV --> Viewer
|
||||
Hub --> Remux --> Storage
|
||||
Viewer --> Playlist --> Storage
|
||||
Viewer --> Segment --> Storage
|
||||
Remote -. 查找 publisher .-> Redis
|
||||
Remote -. FLV relay .-> Hub
|
||||
Remote -. playlist/segment 代理 .-> HLSProxy
|
||||
HLSProxy -. 本地读取 .-> Storage
|
||||
@ -0,0 +1,11 @@
|
||||
flowchart LR
|
||||
Client[客户端应用]
|
||||
Edge[TLS 反向代理<br/>或 Ingress]
|
||||
App[SyncTV]
|
||||
PG[(PostgreSQL)]
|
||||
Redis[(Redis)]
|
||||
|
||||
Client --> Edge
|
||||
Edge --> App
|
||||
App --> PG
|
||||
App --> Redis
|
||||
@ -0,0 +1,18 @@
|
||||
flowchart LR
|
||||
Local[本地第一因素<br/>密码 / OPAQUE / WebAuthn / 邮箱]
|
||||
MFA[MFA 会话<br/>剩余本地验证方式]
|
||||
OAuth2[OAuth2 Provider]
|
||||
Tokens[Access / refresh token<br/>携带认证上下文]
|
||||
API[HTTP / gRPC 业务 API]
|
||||
Management[管理 gRPC<br/>独立运维 token]
|
||||
|
||||
Local -->|开启 2FA| MFA
|
||||
MFA -->|第二因素通过| Tokens
|
||||
Local -->|未开启 2FA| Tokens
|
||||
OAuth2 -->|独立登录信任| Tokens
|
||||
Tokens --> API
|
||||
|
||||
CLI[CLI / 运维人员] --> Management
|
||||
|
||||
Note[OAuth2 不计入本地 2FA 因素。<br/>开启 2FA 的用户仍可通过 OAuth2 登录。]
|
||||
OAuth2 -.-> Note
|
||||
@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <semver-version>" >&2
|
||||
echo "Example: $0 0.2.0" >&2
|
||||
}
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
version="${1#v}"
|
||||
|
||||
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then
|
||||
echo "Invalid release version '$1'. Use SemVer without build metadata, for example 0.2.0 or 0.2.0-rc.1." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
export SYNCTV_RELEASE_VERSION="$version"
|
||||
|
||||
perl -0pi -e '
|
||||
my $version = $ENV{"SYNCTV_RELEASE_VERSION"};
|
||||
s/(\[workspace\.package\]\n(?:(?!\n\[).)*?^version\s*=\s*")[^"]+(")/$1$version$2/ms
|
||||
' Cargo.toml
|
||||
|
||||
perl -0pi -e '
|
||||
my $version = $ENV{"SYNCTV_RELEASE_VERSION"};
|
||||
s/^version:\s*.*/version: $version/m;
|
||||
s/^appVersion:\s*.*/appVersion: "$version"/m;
|
||||
' helm/synctv/Chart.yaml
|
||||
|
||||
cargo_version="$(awk '/^\[workspace.package\]/{in_section=1; next} /^\[/{in_section=0} in_section && $1 == "version" {gsub(/"/, "", $3); print $3; exit}' Cargo.toml)"
|
||||
chart_version="$(sed -n 's/^version:[[:space:]]*//p' helm/synctv/Chart.yaml | head -n1 | tr -d '"')"
|
||||
app_version="$(sed -n 's/^appVersion:[[:space:]]*//p' helm/synctv/Chart.yaml | head -n1 | tr -d '"')"
|
||||
|
||||
if [ "$cargo_version" != "$version" ] || [ "$chart_version" != "$version" ] || [ "$app_version" != "$version" ]; then
|
||||
echo "Failed to synchronize release version across Cargo.toml and helm/synctv/Chart.yaml." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo update --workspace
|
||||
cargo metadata --format-version 1 --no-deps >/dev/null
|
||||
|
||||
stale_lock_versions="$(
|
||||
awk '
|
||||
function flush() {
|
||||
if (name ~ /^synctv/ && version != expected) {
|
||||
print name " " version
|
||||
}
|
||||
}
|
||||
BEGIN {
|
||||
expected = ENVIRON["SYNCTV_RELEASE_VERSION"]
|
||||
}
|
||||
/^\[\[package\]\]/ {
|
||||
flush()
|
||||
name = ""
|
||||
version = ""
|
||||
next
|
||||
}
|
||||
/^name = / {
|
||||
name = $3
|
||||
gsub(/"/, "", name)
|
||||
next
|
||||
}
|
||||
/^version = / {
|
||||
version = $3
|
||||
gsub(/"/, "", version)
|
||||
next
|
||||
}
|
||||
END {
|
||||
flush()
|
||||
}
|
||||
' Cargo.lock
|
||||
)"
|
||||
|
||||
if [ -n "$stale_lock_versions" ]; then
|
||||
echo "Cargo.lock still contains SyncTV workspace packages with stale versions:" >&2
|
||||
echo "$stale_lock_versions" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Synchronized release version $version across Cargo workspace, Cargo.lock, and Helm chart."
|
||||