fix: ci lint

pull/370/head
zijiren233 5 months ago
parent c644f9abc2
commit 2cc58aede8
No known key found for this signature in database
GPG Key ID: 534E082AAA9B39DC

@ -3,6 +3,8 @@ name: Docker
on:
push:
branches: ['**']
tags:
- "v*"
pull_request:
branches: ['**']
schedule:
@ -72,6 +74,7 @@ jobs:
meta-tags: |
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha

@ -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

@ -17,14 +17,29 @@ concurrency:
permissions:
contents: write
packages: write
pull-requests: write
jobs:
helm:
name: Publish Helm Chart
uses: ./.github/workflows/helm.yml
with:
release-tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
HELM_OCI_REPOSITORY: ${{ secrets.HELM_OCI_REPOSITORY }}
HELM_REPOSITORY_URL: ${{ secrets.HELM_REPOSITORY_URL }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_REPOSITORY }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
release:
name: Create GitHub Release
needs: helm
runs-on: ubuntu-latest
env:
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
@ -39,7 +54,16 @@ jobs:
exit 0
fi
gh release create "${RELEASE_TAG}" \
--target "${GITHUB_SHA}" \
--title "${RELEASE_TAG}" \
release_args=(
"${RELEASE_TAG}"
--target "${GITHUB_SHA}"
--title "${RELEASE_TAG}"
--generate-notes
)
version="${RELEASE_TAG#v}"
if [[ "$version" == *-* ]]; then
release_args+=(--prerelease --latest=false)
fi
gh release create "${release_args[@]}"

@ -268,6 +268,11 @@ export default defineConfig({
translations: { en: 'Upgrades and Migrations' },
slug: 'operations/upgrades',
},
{
label: '发布流程',
translations: { en: 'Release Process' },
slug: 'operations/release',
},
{
label: '数据、隐私与保留策略',
translations: { en: 'Data, Privacy, and Retention' },

@ -5,7 +5,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"diagrams": "node scripts/render-diagrams.mjs",
"diagrams": "node scripts/diagrams/render-diagrams.mjs",
"dev": "npm run diagrams && astro dev",
"build": "npm run diagrams && astro build",
"preview": "astro preview",

@ -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],

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

@ -4,8 +4,8 @@ description: 多副本、Redis 协调、节点发现、leader election、静态
---
import Diagram from '../../../components/Diagram.astro';
import clusterRuntimeDark from '../../../assets/diagrams/cluster-runtime-dark.svg';
import clusterRuntimeLight from '../../../assets/diagrams/cluster-runtime-light.svg';
import clusterRuntimeDark from '../../../assets/diagrams/cluster-runtime-zh-dark.svg';
import clusterRuntimeLight from '../../../assets/diagrams/cluster-runtime-zh-light.svg';
import { Aside, Card, CardGrid, Steps } from '@astrojs/starlight/components';
## 什么时候需要集群模式?

@ -4,8 +4,8 @@ description: RTMP、HLS、HTTP-FLV、拉流重试、GOP 缓存和直播存储配
---
import Diagram from '../../../components/Diagram.astro';
import livestreamPipelineDark from '../../../assets/diagrams/livestream-pipeline-dark.svg';
import livestreamPipelineLight from '../../../assets/diagrams/livestream-pipeline-light.svg';
import livestreamPipelineDark from '../../../assets/diagrams/livestream-pipeline-zh-dark.svg';
import livestreamPipelineLight from '../../../assets/diagrams/livestream-pipeline-zh-light.svg';
import { Aside, Card, CardGrid, Steps, TabItem, Tabs } from '@astrojs/starlight/components';
## 直播配置控制什么?

@ -24,7 +24,33 @@ helm/synctv
- ServiceAccount、Role、RoleBinding。
- 可选 metrics、ServiceMonitor、VMServiceScrape、PrometheusRule、NetworkPolicy、HPA、PDB。
## 安装
## 安装发布版 Chart
通过 OCI registry 安装:
```bash
helm install synctv oci://ghcr.io/synctv-org/synctv/charts/synctv \
--version 0.1.0 \
--namespace synctv \
--create-namespace
```
默认父级 OCI repository 是 `ghcr.io/synctv-org/synctv/charts`。Helm 会追加 chart 名,因此安装引用最后仍是 `/synctv`。维护者可以通过 `HELM_OCI_REPOSITORY` 覆盖发布目标。
通过传统 Helm repository 安装:
```bash
helm repo add synctv https://synctv-org.github.io/synctv
helm repo update
helm install synctv synctv/synctv \
--version 0.1.0 \
--namespace synctv \
--create-namespace
```
发布版 Chart 由 release workflow 自动生成。源码仓库只维护 `helm/synctv` 下的 chart 源码;打包后的 `.tgz` 和 Helm repository 的 `index.yaml` 在发布时生成。公开安装要求 GHCR chart package 设为 public,并且 GitHub Pages 使用 `helm-charts` 分支提供内容。
## 从源码安装
```bash
helm install synctv ./helm/synctv \

@ -24,7 +24,33 @@ By default, it can create:
- ServiceAccount, Role, and RoleBinding.
- Optional metrics, ServiceMonitor, VMServiceScrape, PrometheusRule, NetworkPolicy, HPA, and PDB.
## Install
## Install Released Chart
OCI registry install:
```bash
helm install synctv oci://ghcr.io/synctv-org/synctv/charts/synctv \
--version 0.1.0 \
--namespace synctv \
--create-namespace
```
The default parent OCI repository is `ghcr.io/synctv-org/synctv/charts`. Helm appends the chart name, so the install reference ends with `/synctv`. Maintainers can override the publishing target with `HELM_OCI_REPOSITORY`.
Traditional Helm repository install:
```bash
helm repo add synctv https://synctv-org.github.io/synctv
helm repo update
helm install synctv synctv/synctv \
--version 0.1.0 \
--namespace synctv \
--create-namespace
```
Published charts are generated by the release workflow. The source repository keeps only the chart source under `helm/synctv`; packaged `.tgz` files and the Helm repository `index.yaml` are generated during release. Public installs require the GHCR chart package to be public and GitHub Pages to serve the `helm-charts` branch.
## Install From Source
```bash
helm install synctv ./helm/synctv \

@ -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`. |

@ -4,12 +4,12 @@ description: SyncTV 的进程边界、核心依赖、网络入口、存储层、
---
import Diagram from '../../../components/Diagram.astro';
import architectureDiagramDark from '../../../assets/diagrams/architecture-dark.svg';
import architectureDiagramLight from '../../../assets/diagrams/architecture-light.svg';
import kubernetesTopologyDark from '../../../assets/diagrams/kubernetes-topology-dark.svg';
import kubernetesTopologyLight from '../../../assets/diagrams/kubernetes-topology-light.svg';
import productionMinimalDark from '../../../assets/diagrams/production-minimal-dark.svg';
import productionMinimalLight from '../../../assets/diagrams/production-minimal-light.svg';
import architectureDiagramDark from '../../../assets/diagrams/architecture-zh-dark.svg';
import architectureDiagramLight from '../../../assets/diagrams/architecture-zh-light.svg';
import kubernetesTopologyDark from '../../../assets/diagrams/kubernetes-topology-zh-dark.svg';
import kubernetesTopologyLight from '../../../assets/diagrams/kubernetes-topology-zh-light.svg';
import productionMinimalDark from '../../../assets/diagrams/production-minimal-zh-dark.svg';
import productionMinimalLight from '../../../assets/diagrams/production-minimal-zh-light.svg';
import { Aside, Card, CardGrid, Steps, TabItem, Tabs } from '@astrojs/starlight/components';
## 一句话模型

@ -4,8 +4,8 @@ description: SyncTV 的登录方式、2FA 语义、token 上下文、管理权
---
import Diagram from '../../../components/Diagram.astro';
import securityAuthBoundaryDiagramDark from '../../../assets/diagrams/security-auth-boundary-dark.svg';
import securityAuthBoundaryDiagramLight from '../../../assets/diagrams/security-auth-boundary-light.svg';
import securityAuthBoundaryDiagramDark from '../../../assets/diagrams/security-auth-boundary-zh-dark.svg';
import securityAuthBoundaryDiagramLight from '../../../assets/diagrams/security-auth-boundary-zh-light.svg';
import { Aside, Card, CardGrid, Steps } from '@astrojs/starlight/components';
## 安全边界

@ -0,0 +1,58 @@
---
title: 发布流程
description: 如何在 Cargo、镜像和 Helm chart 之间准备并发布 SyncTV 版本。
---
SyncTV 在 Rust workspace、容器镜像和 Helm chart 之间使用同一个应用版本。
## 准备发布
先通过 GitHub Actions 打开版本准备 PR:
1. 运行 `Prepare Release` workflow。
2. 输入下一个版本,例如 `0.2.0`。
3. review 并合并自动生成的 PR。
workflow 会运行:
```bash
./scripts/set-release-version.sh 0.2.0
```
它会同步:
- `Cargo.toml` 的 `[workspace.package].version`
- `Cargo.lock` 中本 workspace path package 的版本
- `helm/synctv/Chart.yaml` 的 `version`
- `helm/synctv/Chart.yaml` 的 `appVersion`
## 发布
版本准备 PR 合并后,创建并推送匹配的 tag:
```bash
git tag v0.2.0
git push origin v0.2.0
```
`Release` workflow 会调用只能通过 `workflow_call` 触发的 `Helm Release` workflow。Helm workflow 会校验 tag、Cargo workspace version、Helm chart version 和 Helm `appVersion` 全部一致。
workflow 会发布:
- GitHub release。
- OCI Helm chart。默认位置为 `ghcr.io/<owner>/<repo>/charts`,本仓库默认是 `ghcr.io/synctv-org/synctv/charts`。
- 一个指向 `helm-charts` 分支的 PR,包含打包后的 chart 和更新后的 `index.yaml`。
传统 Helm repository 要在 `helm-charts` PR 合并,并且 GitHub Pages 使用该分支提供内容后才会更新。
## Helm 发布目标
Helm OCI 发布目标参考 Docker workflow 的变量风格:默认使用当前 GitHub 仓库名生成 GHCR 路径;如果配置了 DockerHub 变量,也会发布到对应 DockerHub charts 路径;也可以用 repository variables 或 secrets 显式覆盖。
| 变量 | 默认值 | 作用 |
| --- | --- | --- |
| `HELM_OCI_REPOSITORY` | 自动推导 | 显式 Helm OCI 父级 repository 覆盖值。设置后只发布到这里;安装时 Helm 会继续追加 chart 名。 |
| `DOCKERHUB_USERNAME` | 空 | 与 Docker workflow 相同;设置后额外发布到 `docker.io/<username>/<repo>/charts`。 |
| `DOCKERHUB_REPOSITORY` | 空 | 与 Docker workflow 相同;可覆盖 DockerHub repository 名。 |
| `HELM_REPOSITORY_BRANCH` | `helm-charts` | 传统 Helm repository 分支。不存在时 workflow 会自动初始化。 |
| `HELM_REPOSITORY_URL` | `https://<owner>.github.io/<repo>` | `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

@ -38,12 +38,42 @@ The default installation deploys:
These database services are internal-only and are not exposed outside the cluster. For temporary external access, prefer `kubectl port-forward`.
The chart defaults to single-replica mode with `config.cluster.enabled=false`. Before scaling beyond one replica, enable cluster mode. Local HLS backends work through publisher-node gRPC proxying, while `file` with a real shared filesystem (`persistence.hls.existingClaim`) or `oss` with S3-compatible object storage is recommended for production HLS traffic.
Install the released OCI chart:
```bash
helm install synctv oci://ghcr.io/synctv-org/synctv/charts/synctv \
--version 0.1.0 \
--namespace synctv \
--create-namespace
```
The default parent OCI repository is `ghcr.io/synctv-org/synctv/charts`. Helm
appends the chart name, so the install reference ends with `/synctv`.
Maintainers can override the publishing target with `HELM_OCI_REPOSITORY`.
Or install from the traditional Helm repository:
```bash
helm repo add synctv https://synctv-org.github.io/synctv
helm repo update
helm install synctv synctv/synctv \
--version 0.1.0 \
--namespace synctv \
--create-namespace
```
For local development, install from the chart source:
```bash
helm install synctv ./helm/synctv \
--namespace synctv \
--create-namespace
```
Released charts are generated by the release workflow. Public installs require
the GHCR chart package to be public and GitHub Pages to serve the `helm-charts`
branch.
At minimum, production deployments should override these secrets:
```yaml

@ -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."
Loading…
Cancel
Save