feat: 增加 Gitea webhook relay 诊断
- Gitea client: 新增 hooks 读取并只保留安全诊断字段 - Projects: 在 Gitea 诊断中补充 webhook/relay 匹配状态 - Settings: 增加 GITEA_WEBHOOK_RELAY_BASE_URL 配置说明
This commit is contained in:
@@ -24,6 +24,7 @@ JENKINS_AUTO_SYNC_INTERVAL_MS=30000
|
|||||||
GITEA_BASE_URL=https://gitea.example.internal
|
GITEA_BASE_URL=https://gitea.example.internal
|
||||||
GITEA_TOKEN=replace-with-secret-manager-value
|
GITEA_TOKEN=replace-with-secret-manager-value
|
||||||
GITEA_WEBHOOK_SECRET=replace-with-secret-manager-value
|
GITEA_WEBHOOK_SECRET=replace-with-secret-manager-value
|
||||||
|
GITEA_WEBHOOK_RELAY_BASE_URL=https://jenkins.example.internal/gitea-jenkins-notify
|
||||||
|
|
||||||
# Public platform URL used in notification links.
|
# Public platform URL used in notification links.
|
||||||
DEVOPS_PUBLIC_URL=https://devops.mrzhan.top
|
DEVOPS_PUBLIC_URL=https://devops.mrzhan.top
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ pnpm prisma:seed
|
|||||||
- `USE_DATABASE_READS`:是否启用 Prisma 数据读写。
|
- `USE_DATABASE_READS`:是否启用 Prisma 数据读写。
|
||||||
- `PRISMA_CONNECT_ON_BOOT`:启动时是否主动连接数据库。
|
- `PRISMA_CONNECT_ON_BOOT`:启动时是否主动连接数据库。
|
||||||
- `JENKINS_BASE_URL`、`JENKINS_USERNAME`、`JENKINS_API_TOKEN`:Jenkins 集成配置。
|
- `JENKINS_BASE_URL`、`JENKINS_USERNAME`、`JENKINS_API_TOKEN`:Jenkins 集成配置。
|
||||||
- `GITEA_BASE_URL`、`GITEA_TOKEN`、`GITEA_WEBHOOK_SECRET`:Gitea 集成配置。
|
- `GITEA_BASE_URL`、`GITEA_TOKEN`、`GITEA_WEBHOOK_SECRET`、`GITEA_WEBHOOK_RELAY_BASE_URL`:Gitea 集成与 Jenkins relay 诊断配置。
|
||||||
- `NOTIFICATION_PROVIDER`、`WECOM_WEBHOOK_URL`、`FEISHU_WEBHOOK_URL`、`NOTIFICATION_WEBHOOK_URL`:通知集成配置。
|
- `NOTIFICATION_PROVIDER`、`WECOM_WEBHOOK_URL`、`FEISHU_WEBHOOK_URL`、`NOTIFICATION_WEBHOOK_URL`:通知集成配置。
|
||||||
- `LLM_BASE_URL`、`LLM_API_KEY`、`LLM_MODEL`:Agent 配置的环境变量后备值。
|
- `LLM_BASE_URL`、`LLM_API_KEY`、`LLM_MODEL`:Agent 配置的环境变量后备值。
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export const envSchema = z.object({
|
|||||||
GITEA_BASE_URL: optionalUrl,
|
GITEA_BASE_URL: optionalUrl,
|
||||||
GITEA_TOKEN: optionalString,
|
GITEA_TOKEN: optionalString,
|
||||||
GITEA_WEBHOOK_SECRET: optionalString,
|
GITEA_WEBHOOK_SECRET: optionalString,
|
||||||
|
GITEA_WEBHOOK_RELAY_BASE_URL: optionalUrl,
|
||||||
DEVOPS_PUBLIC_URL: optionalUrl,
|
DEVOPS_PUBLIC_URL: optionalUrl,
|
||||||
NOTIFICATION_PROVIDER: optionalString,
|
NOTIFICATION_PROVIDER: optionalString,
|
||||||
NOTIFICATION_WEBHOOK_URL: optionalUrl,
|
NOTIFICATION_WEBHOOK_URL: optionalUrl,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
GiteaBranch,
|
GiteaBranch,
|
||||||
GiteaCommitSummary,
|
GiteaCommitSummary,
|
||||||
GiteaPullRequestSummary,
|
GiteaPullRequestSummary,
|
||||||
|
GiteaRepositoryHook,
|
||||||
GiteaRepositoryInfo,
|
GiteaRepositoryInfo,
|
||||||
GiteaTag,
|
GiteaTag,
|
||||||
} from './gitea.types';
|
} from './gitea.types';
|
||||||
@@ -78,6 +79,35 @@ export class GiteaClient {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listHooks(owner: string, repo: string): Promise<GiteaRepositoryHook[]> {
|
||||||
|
const payload = await this.request<
|
||||||
|
Array<{
|
||||||
|
id: number;
|
||||||
|
type?: string;
|
||||||
|
name?: string;
|
||||||
|
active?: boolean;
|
||||||
|
events?: string[];
|
||||||
|
branch_filter?: string;
|
||||||
|
config?: {
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
}>
|
||||||
|
>(`/api/v1/repos/${owner}/${repo}/hooks`);
|
||||||
|
|
||||||
|
return payload.map((hook) => ({
|
||||||
|
id: hook.id,
|
||||||
|
type: hook.type ?? hook.name ?? 'unknown',
|
||||||
|
active: hook.active === true,
|
||||||
|
events: Array.isArray(hook.events) ? hook.events : [],
|
||||||
|
branchFilter: hook.branch_filter,
|
||||||
|
targetUrl: hook.config?.url,
|
||||||
|
createdAt: hook.created_at,
|
||||||
|
updatedAt: hook.updated_at,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
async getCommit(
|
async getCommit(
|
||||||
owner: string,
|
owner: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ export type GiteaPullRequestSummary = {
|
|||||||
authorName?: string;
|
authorName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GiteaRepositoryHook = {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
active: boolean;
|
||||||
|
events: string[];
|
||||||
|
branchFilter?: string;
|
||||||
|
targetUrl?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type GiteaRepositoryInfo = {
|
export type GiteaRepositoryInfo = {
|
||||||
owner: string;
|
owner: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -93,6 +93,30 @@ export type ProjectGiteaRepositoryDiagnostic = {
|
|||||||
authorName?: string;
|
authorName?: string;
|
||||||
authoredAt?: string;
|
authoredAt?: string;
|
||||||
};
|
};
|
||||||
|
webhook?: ProjectGiteaWebhookDiagnostic;
|
||||||
|
updatedAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProjectGiteaWebhookDiagnostic = {
|
||||||
|
status: 'ok' | 'missing' | 'not_configured' | 'unavailable';
|
||||||
|
message?: string;
|
||||||
|
hookCount: number;
|
||||||
|
activePushHookCount: number;
|
||||||
|
expectedRelayConfigured: boolean;
|
||||||
|
expectedRelayTarget?: string;
|
||||||
|
matchedHookId?: number;
|
||||||
|
hooks: ProjectGiteaWebhookHookDiagnostic[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProjectGiteaWebhookHookDiagnostic = {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
active: boolean;
|
||||||
|
events: string[];
|
||||||
|
branchFilter?: string;
|
||||||
|
target?: string;
|
||||||
|
matchesExpectedRelay: boolean;
|
||||||
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,24 @@
|
|||||||
* 项目服务统一处理项目权限、Gitea 元数据读取和 Jenkins/Gitea 诊断聚合。
|
* 项目服务统一处理项目权限、Gitea 元数据读取和 Jenkins/Gitea 诊断聚合。
|
||||||
*/
|
*/
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
import { AuthenticatedUser } from '../auth/auth.types';
|
import { AuthenticatedUser } from '../auth/auth.types';
|
||||||
import { AppError } from '../common/errors/app-error';
|
import { AppError } from '../common/errors/app-error';
|
||||||
import { IntegrationRequestError } from '../common/errors/integration-error';
|
import { IntegrationRequestError } from '../common/errors/integration-error';
|
||||||
|
import { EnvConfig } from '../config/env.schema';
|
||||||
import { GiteaClient } from '../integrations/gitea/gitea.client';
|
import { GiteaClient } from '../integrations/gitea/gitea.client';
|
||||||
import {
|
import {
|
||||||
GiteaCommitSummary,
|
GiteaCommitSummary,
|
||||||
GiteaPullRequestSummary,
|
GiteaPullRequestSummary,
|
||||||
|
GiteaRepositoryHook,
|
||||||
} from '../integrations/gitea/gitea.types';
|
} from '../integrations/gitea/gitea.types';
|
||||||
import { JenkinsClient } from '../integrations/jenkins/jenkins.client';
|
import { JenkinsClient } from '../integrations/jenkins/jenkins.client';
|
||||||
import { ProjectRepository } from './project.repository';
|
import { ProjectRepository } from './project.repository';
|
||||||
import {
|
import {
|
||||||
ProjectGiteaDiagnosticsSummary,
|
ProjectGiteaDiagnosticsSummary,
|
||||||
ProjectGiteaRepositoryDiagnostic,
|
ProjectGiteaRepositoryDiagnostic,
|
||||||
|
ProjectGiteaWebhookHookDiagnostic,
|
||||||
ProjectJenkinsDiagnosticsSummary,
|
ProjectJenkinsDiagnosticsSummary,
|
||||||
ProjectJenkinsJobDiagnostic,
|
ProjectJenkinsJobDiagnostic,
|
||||||
ProjectRefSummary,
|
ProjectRefSummary,
|
||||||
@@ -35,6 +39,7 @@ export class ProjectsService {
|
|||||||
private readonly giteaClient: GiteaClient,
|
private readonly giteaClient: GiteaClient,
|
||||||
private readonly jenkinsClient: JenkinsClient,
|
private readonly jenkinsClient: JenkinsClient,
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
|
private readonly config: ConfigService<EnvConfig, true>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async listProjects(user?: AuthenticatedUser): Promise<ProjectSummary[]> {
|
async listProjects(user?: AuthenticatedUser): Promise<ProjectSummary[]> {
|
||||||
@@ -159,6 +164,7 @@ export class ProjectsService {
|
|||||||
repo: repo?.name,
|
repo: repo?.name,
|
||||||
status: 'not_configured',
|
status: 'not_configured',
|
||||||
message: health.message,
|
message: health.message,
|
||||||
|
webhook: this.giteaWebhookNotConfigured(health.message),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -296,6 +302,7 @@ export class ProjectsService {
|
|||||||
repositoryUrl: project.repositoryUrl,
|
repositoryUrl: project.repositoryUrl,
|
||||||
status: 'unavailable',
|
status: 'unavailable',
|
||||||
message: 'Gitea 仓库地址无法解析',
|
message: 'Gitea 仓库地址无法解析',
|
||||||
|
webhook: this.giteaWebhookUnavailable('仓库地址无法解析,无法读取 webhook'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,10 +313,10 @@ export class ProjectsService {
|
|||||||
this.giteaClient.listTags(repo.owner, repo.name),
|
this.giteaClient.listTags(repo.owner, repo.name),
|
||||||
]);
|
]);
|
||||||
const defaultBranch = repository.defaultBranch ?? project.defaultBranch;
|
const defaultBranch = repository.defaultBranch ?? project.defaultBranch;
|
||||||
const latestCommit = await this.getRepositoryDefaultCommit(
|
const [latestCommit, webhook] = await Promise.all([
|
||||||
repo,
|
this.getRepositoryDefaultCommit(repo, defaultBranch),
|
||||||
defaultBranch,
|
this.getRepositoryWebhookDiagnostic(repo),
|
||||||
);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projectKey: project.key,
|
projectKey: project.key,
|
||||||
@@ -327,6 +334,7 @@ export class ProjectsService {
|
|||||||
branchCount: branches.length,
|
branchCount: branches.length,
|
||||||
tagCount: tags.length,
|
tagCount: tags.length,
|
||||||
latestCommit,
|
latestCommit,
|
||||||
|
webhook,
|
||||||
updatedAt: repository.updatedAt,
|
updatedAt: repository.updatedAt,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -341,6 +349,7 @@ export class ProjectsService {
|
|||||||
repo: repo.name,
|
repo: repo.name,
|
||||||
status: 'missing',
|
status: 'missing',
|
||||||
message: 'Gitea 仓库不存在或当前 token 无权访问',
|
message: 'Gitea 仓库不存在或当前 token 无权访问',
|
||||||
|
webhook: this.giteaWebhookUnavailable('仓库不可达,无法读取 webhook'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,10 +360,177 @@ export class ProjectsService {
|
|||||||
repo: repo.name,
|
repo: repo.name,
|
||||||
status: 'unavailable',
|
status: 'unavailable',
|
||||||
message: error instanceof Error ? error.message : 'Gitea 诊断失败',
|
message: error instanceof Error ? error.message : 'Gitea 诊断失败',
|
||||||
|
webhook: this.giteaWebhookUnavailable('仓库诊断失败,无法读取 webhook'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webhook 诊断只暴露 active/push/目标 host+path 等安全字段,不返回 secret、query 或原始 config。
|
||||||
|
*/
|
||||||
|
private async getRepositoryWebhookDiagnostic(
|
||||||
|
repo: ParsedRepository,
|
||||||
|
): Promise<ProjectGiteaRepositoryDiagnostic['webhook']> {
|
||||||
|
try {
|
||||||
|
const hooks = await this.giteaClient.listHooks(repo.owner, repo.name);
|
||||||
|
const expectedRelayTarget = this.expectedWebhookRelayTarget(repo.name);
|
||||||
|
const normalizedExpected = this.normalizeWebhookTarget(expectedRelayTarget);
|
||||||
|
const hookDiagnostics = hooks.map((hook) =>
|
||||||
|
this.toWebhookHookDiagnostic(hook, normalizedExpected),
|
||||||
|
);
|
||||||
|
const activePushHookCount = hookDiagnostics.filter(
|
||||||
|
(hook) => hook.active && hook.events.includes('push'),
|
||||||
|
).length;
|
||||||
|
const matchedHook = hookDiagnostics.find(
|
||||||
|
(hook) => hook.active && hook.events.includes('push') && hook.matchesExpectedRelay,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!expectedRelayTarget) {
|
||||||
|
return {
|
||||||
|
status: 'not_configured',
|
||||||
|
message: '未配置 GITEA_WEBHOOK_RELAY_BASE_URL,无法校验 Jenkins relay',
|
||||||
|
hookCount: hooks.length,
|
||||||
|
activePushHookCount,
|
||||||
|
expectedRelayConfigured: false,
|
||||||
|
hooks: hookDiagnostics,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchedHook) {
|
||||||
|
return {
|
||||||
|
status: 'missing',
|
||||||
|
message:
|
||||||
|
activePushHookCount > 0
|
||||||
|
? '未发现匹配预期 Jenkins relay 的 active push hook'
|
||||||
|
: '未发现 active push hook',
|
||||||
|
hookCount: hooks.length,
|
||||||
|
activePushHookCount,
|
||||||
|
expectedRelayConfigured: true,
|
||||||
|
expectedRelayTarget,
|
||||||
|
hooks: hookDiagnostics,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
hookCount: hooks.length,
|
||||||
|
activePushHookCount,
|
||||||
|
expectedRelayConfigured: true,
|
||||||
|
expectedRelayTarget,
|
||||||
|
matchedHookId: matchedHook.id,
|
||||||
|
hooks: hookDiagnostics,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return this.giteaWebhookUnavailable(
|
||||||
|
error instanceof Error ? error.message : 'Gitea webhook 诊断失败',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toWebhookHookDiagnostic(
|
||||||
|
hook: GiteaRepositoryHook,
|
||||||
|
normalizedExpected?: string,
|
||||||
|
): ProjectGiteaWebhookHookDiagnostic {
|
||||||
|
const target = this.sanitizeWebhookTarget(hook.targetUrl);
|
||||||
|
const normalizedTarget = this.normalizeWebhookTarget(target);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: hook.id,
|
||||||
|
type: hook.type,
|
||||||
|
active: hook.active,
|
||||||
|
events: hook.events,
|
||||||
|
branchFilter: hook.branchFilter,
|
||||||
|
target,
|
||||||
|
matchesExpectedRelay:
|
||||||
|
Boolean(normalizedExpected) && normalizedTarget === normalizedExpected,
|
||||||
|
createdAt: hook.createdAt,
|
||||||
|
updatedAt: hook.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private expectedWebhookRelayTarget(repoName: string): string | undefined {
|
||||||
|
const relayBaseUrl = this.config.get('GITEA_WEBHOOK_RELAY_BASE_URL', {
|
||||||
|
infer: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!relayBaseUrl) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sanitizeWebhookTarget(
|
||||||
|
`${relayBaseUrl.replace(/\/+$/, '')}/${encodeURIComponent(repoName)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sanitizeWebhookTarget(value?: string): string | undefined {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
const pathname = this.redactWebhookPath(url.pathname);
|
||||||
|
return `${url.protocol}//${url.host}${pathname}`.replace(/\/+$/, '');
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeWebhookTarget(value?: string): string | undefined {
|
||||||
|
return this.sanitizeWebhookTarget(value)?.replace(/\/+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
private redactWebhookPath(pathname: string): string {
|
||||||
|
const segments = pathname.split('/').map((segment) => {
|
||||||
|
if (!segment) {
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedSegment = this.safeDecodeURIComponent(segment).toLowerCase();
|
||||||
|
|
||||||
|
return /token|secret|key|password|signature|auth/.test(decodedSegment) ||
|
||||||
|
decodedSegment.length > 96
|
||||||
|
? '[redacted]'
|
||||||
|
: segment;
|
||||||
|
});
|
||||||
|
|
||||||
|
return segments.join('/') || '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
private safeDecodeURIComponent(value: string): string {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(value);
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private giteaWebhookNotConfigured(
|
||||||
|
message?: string,
|
||||||
|
): ProjectGiteaRepositoryDiagnostic['webhook'] {
|
||||||
|
return {
|
||||||
|
status: 'not_configured',
|
||||||
|
message,
|
||||||
|
hookCount: 0,
|
||||||
|
activePushHookCount: 0,
|
||||||
|
expectedRelayConfigured: false,
|
||||||
|
hooks: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private giteaWebhookUnavailable(
|
||||||
|
message?: string,
|
||||||
|
): ProjectGiteaRepositoryDiagnostic['webhook'] {
|
||||||
|
return {
|
||||||
|
status: 'unavailable',
|
||||||
|
message,
|
||||||
|
hookCount: 0,
|
||||||
|
activePushHookCount: 0,
|
||||||
|
expectedRelayConfigured: false,
|
||||||
|
hooks: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认分支 commit 摘要只是展示增强;读取失败不影响仓库可达性诊断。
|
* 默认分支 commit 摘要只是展示增强;读取失败不影响仓库可达性诊断。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
|
|||||||
key: 'gitea',
|
key: 'gitea',
|
||||||
name: 'Gitea',
|
name: 'Gitea',
|
||||||
required: ['GITEA_BASE_URL', 'GITEA_TOKEN'],
|
required: ['GITEA_BASE_URL', 'GITEA_TOKEN'],
|
||||||
optional: ['GITEA_WEBHOOK_SECRET'],
|
optional: ['GITEA_WEBHOOK_SECRET', 'GITEA_WEBHOOK_RELAY_BASE_URL'],
|
||||||
note: 'GITEA_WEBHOOK_SECRET 用于开启 webhook 签名校验。',
|
note: 'GITEA_WEBHOOK_SECRET 用于开启 webhook 签名校验;GITEA_WEBHOOK_RELAY_BASE_URL 用于校验仓库 push hook 是否已接入 Jenkins relay。',
|
||||||
variableHelp: {
|
variableHelp: {
|
||||||
GITEA_BASE_URL: variableHelp('Gitea 服务端 API 地址,用于读取仓库、分支和 tag。'),
|
GITEA_BASE_URL: variableHelp('Gitea 服务端 API 地址,用于读取仓库、分支和 tag。'),
|
||||||
GITEA_TOKEN: variableHelp(
|
GITEA_TOKEN: variableHelp(
|
||||||
@@ -65,6 +65,10 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
|
|||||||
GITEA_WEBHOOK_SECRET: variableHelp(
|
GITEA_WEBHOOK_SECRET: variableHelp(
|
||||||
'Gitea push webhook 签名密钥;配置后后端会校验 webhook 来源。',
|
'Gitea push webhook 签名密钥;配置后后端会校验 webhook 来源。',
|
||||||
),
|
),
|
||||||
|
GITEA_WEBHOOK_RELAY_BASE_URL: variableHelp(
|
||||||
|
'Jenkins relay 的公开 webhook 基地址,用于诊断仓库是否存在匹配的 active push hook。',
|
||||||
|
{ example: 'https://jenkins.ops.mrzhan.top/gitea-jenkins-notify' },
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user