2
0

docs(plugins): add comprehensive plugin development guide

Add PLUGINS.md with complete documentation for building external GitCaddy plugins using the gRPC-based plugin protocol.

Documentation includes:
- Protocol overview and service definition
- Lifecycle diagram (Initialize → HealthCheck → OnEvent/HandleHTTP → Shutdown)
- Complete message reference for all 6 RPC methods
- Plugin manifest specification (routes, events, permissions, license tiers)
- Health monitoring and auto-restart behavior
- Configuration guide for external vs managed mode
- Transport details (h2c/HTTP2, gRPC wire format)
- Full working examples in Go, C#, and Python
- Debugging tips and common issues

Also updates README.md to reference the plugin guide and removes outdated Chinese translations (zh-cn, zh-tw) that were not being maintained.

This provides plugin developers with everything needed to build and deploy external services that integrate with GitCaddy's plugin framework.
This commit is contained in:
2026-02-13 01:54:25 -05:00
parent 174d18db22
commit e932582f54
4 changed files with 617 additions and 412 deletions

550
PLUGINS.md Normal file
View File

@@ -0,0 +1,550 @@
# GitCaddy Plugin Development Guide
This guide explains how to build external plugins for GitCaddy. Plugins are standalone services that communicate with the server over gRPC (HTTP/2) using a well-defined protocol.
## Table of Contents
- [Overview](#overview)
- [Protocol](#protocol)
- [Service Definition](#service-definition)
- [Lifecycle](#lifecycle)
- [Messages](#messages)
- [Plugin Manifest](#plugin-manifest)
- [Routes](#routes)
- [Events](#events)
- [Permissions](#permissions)
- [Health Monitoring](#health-monitoring)
- [Configuration](#configuration)
- [External Mode](#external-mode)
- [Managed Mode](#managed-mode)
- [Configuration Reference](#configuration-reference)
- [Transport](#transport)
- [Example: Go Plugin](#example-go-plugin)
- [Example: C# Plugin](#example-c-plugin)
- [Example: Python Plugin](#example-python-plugin)
- [Debugging](#debugging)
## Overview
A GitCaddy plugin is any process that implements the `PluginService` gRPC interface. The server connects to the plugin on startup, calls `Initialize` to get its manifest, and then:
- **Health checks** the plugin periodically (default: every 30 seconds)
- **Dispatches events** the plugin has subscribed to (e.g., `license:updated`)
- **Proxies HTTP requests** to routes the plugin has declared
- **Shuts down** the plugin gracefully when the server stops
Plugins can run in two modes:
- **External mode** - The plugin runs independently (Docker, systemd, etc.). The server connects to it.
- **Managed mode** - The server launches the plugin binary and manages its process lifecycle.
## Protocol
The protocol is defined in [`modules/plugins/pluginv1/plugin.proto`](modules/plugins/pluginv1/plugin.proto).
### Service Definition
```protobuf
service PluginService {
rpc Initialize(InitializeRequest) returns (InitializeResponse);
rpc Shutdown(ShutdownRequest) returns (ShutdownResponse);
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
rpc GetManifest(GetManifestRequest) returns (PluginManifest);
rpc OnEvent(PluginEvent) returns (EventResponse);
rpc HandleHTTP(HTTPRequest) returns (HTTPResponse);
}
```
All 6 RPCs are unary (request-response). The server is the client; the plugin is the server.
### Lifecycle
```
Server starts
Initialize(server_version, config)
│ Plugin returns: success + PluginManifest
Plugin is ONLINE
├──► HealthCheck() every 30s
│ Plugin returns: healthy, status, details
├──► OnEvent(event_type, payload, repo_id, org_id)
│ Dispatched for subscribed events (fire-and-forget with 30s timeout)
├──► HandleHTTP(method, path, headers, body)
│ Proxied when an incoming request matches a declared route
Server shutting down
Shutdown(reason)
│ Plugin returns: success
Plugin process is sent SIGINT (managed mode only)
```
### Messages
#### InitializeRequest
| Field | Type | Description |
|-------|------|-------------|
| `server_version` | string | The GitCaddy server version (e.g., `"3.0.0"`) |
| `config` | map<string, string> | Server-provided configuration key-value pairs |
#### InitializeResponse
| Field | Type | Description |
|-------|------|-------------|
| `success` | bool | Whether initialization succeeded |
| `error` | string | Error message if `success` is false |
| `manifest` | PluginManifest | The plugin's capability manifest |
#### HealthCheckRequest
Empty message. No fields.
#### HealthCheckResponse
| Field | Type | Description |
|-------|------|-------------|
| `healthy` | bool | Whether the plugin considers itself healthy |
| `status` | string | Human-readable status (e.g., `"operational"`, `"degraded"`) |
| `details` | map<string, string> | Arbitrary key-value details (version, uptime, etc.) |
#### PluginEvent
| Field | Type | Description |
|-------|------|-------------|
| `event_type` | string | The event name (e.g., `"license:updated"`, `"repo:push"`) |
| `payload` | google.protobuf.Struct | Event-specific data as a JSON-like structure |
| `timestamp` | google.protobuf.Timestamp | When the event occurred |
| `repo_id` | int64 | Repository ID (0 if not repo-specific) |
| `org_id` | int64 | Organization ID (0 if not org-specific) |
#### EventResponse
| Field | Type | Description |
|-------|------|-------------|
| `handled` | bool | Whether the plugin handled the event |
| `error` | string | Error message if handling failed |
#### HTTPRequest / HTTPResponse
| Field | Type | Description |
|-------|------|-------------|
| `method` | string | HTTP method (`GET`, `POST`, etc.) |
| `path` | string | Request path (e.g., `/api/v1/health`) |
| `headers` | map<string, string> | HTTP headers |
| `body` | bytes | Request/response body |
| `query_params` | map<string, string> | Query parameters (request only) |
| `status_code` | int32 | HTTP status code (response only) |
## Plugin Manifest
The manifest declares what your plugin does. It is returned during `Initialize` and can be re-fetched via `GetManifest`.
```protobuf
message PluginManifest {
string name = 1;
string version = 2;
string description = 3;
repeated string subscribed_events = 4;
repeated PluginRoute routes = 5;
repeated string required_permissions = 6;
string license_tier = 7;
}
```
| Field | Description | Example |
|-------|-------------|---------|
| `name` | Display name | `"My Analytics Plugin"` |
| `version` | Semver version | `"1.2.0"` |
| `description` | What the plugin does | `"Tracks repository analytics"` |
| `subscribed_events` | Events to receive | `["repo:push", "issue:created"]` |
| `routes` | HTTP routes the plugin handles | See below |
| `required_permissions` | Permissions the plugin needs | `["repo:read", "issue:write"]` |
| `license_tier` | Minimum license tier required | `"standard"`, `"professional"`, `"enterprise"` |
### Routes
Routes declare which HTTP paths your plugin handles. When the server receives a request matching a plugin's route, it proxies the request via `HandleHTTP`.
```protobuf
message PluginRoute {
string method = 1; // "GET", "POST", etc.
string path = 2; // "/api/v1/my-plugin/endpoint"
string description = 3;
}
```
Route matching uses prefix matching: a declared path of `/api/v1/analytics` will match `/api/v1/analytics/events`.
### Events
Subscribe to server events by listing them in `subscribed_events`. Use `"*"` to subscribe to all events.
Available events include:
- `license:updated` - License key changed
- `repo:push` - Code pushed to a repository
- `repo:created` - New repository created
- `issue:created` - New issue opened
- `issue:comment` - Comment added to an issue
- `pull_request:opened` - New pull request opened
- `pull_request:merged` - Pull request merged
Events are dispatched asynchronously (fire-and-forget) with a 30-second timeout per plugin.
### Permissions
The `required_permissions` field declares what server resources your plugin needs access to. The server logs these at startup for admin review.
## Health Monitoring
The server health-checks every registered plugin at a configurable interval (default: 30 seconds).
**Behavior:**
| Consecutive Failures | Action |
|---------------------|--------|
| 1-2 | Warning logged, plugin stays online |
| 3+ | Plugin marked **offline**, error logged |
| 3+ (managed mode) | Automatic restart attempted |
When a previously offline plugin responds to a health check, it is marked **online** and an info log is emitted.
If `HealthCheckResponse.healthy` is `false` (the RPC succeeds but the plugin reports unhealthy), the plugin is marked as **error** status. This allows plugins to report degraded operation (e.g., missing API key, expired license) without being treated as crashed.
**Health check timeout** is configured per-plugin via `HEALTH_TIMEOUT` (default: 5 seconds).
## Configuration
Plugins are configured in the server's `app.ini`.
### External Mode
The plugin runs independently. The server connects to its gRPC endpoint.
```ini
[plugins]
ENABLED = true
HEALTH_CHECK_INTERVAL = 30s
[plugins.my-plugin]
ENABLED = true
ADDRESS = localhost:9090
HEALTH_TIMEOUT = 5s
SUBSCRIBED_EVENTS = repo:push, issue:created
```
### Managed Mode
The server launches the plugin binary and manages its lifecycle. If the plugin crashes, the server restarts it automatically.
```ini
[plugins.my-plugin]
ENABLED = true
BINARY = /opt/plugins/my-plugin
ARGS = --port 9090 --log-level info
ADDRESS = localhost:9090
HEALTH_TIMEOUT = 5s
```
When `BINARY` is set, the server:
1. Starts the process with the specified arguments
2. Waits 2 seconds for the process to initialize
3. Calls `Initialize` via gRPC
4. Sends `SIGINT` on server shutdown
5. Auto-restarts the process if health checks fail 3 consecutive times
### Configuration Reference
#### `[plugins]` Section
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `ENABLED` | bool | `true` | Master switch for the plugin framework |
| `PATH` | string | `data/plugins` | Directory for plugin data |
| `HEALTH_CHECK_INTERVAL` | duration | `30s` | How often to health-check plugins |
#### `[plugins.<name>]` Section
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `ENABLED` | bool | `true` | Whether this plugin is active |
| `ADDRESS` | string | (required) | gRPC endpoint (e.g., `localhost:9090`) |
| `BINARY` | string | (optional) | Path to plugin binary (enables managed mode) |
| `ARGS` | string | (optional) | Arguments for the binary |
| `HEALTH_TIMEOUT` | duration | `5s` | Timeout for health check RPCs |
| `SUBSCRIBED_EVENTS` | string | (optional) | Comma-separated event names |
A plugin must have either `BINARY` or `ADDRESS` (or both for managed mode). Entries with neither are skipped with a warning.
## Transport
Plugins communicate over **cleartext HTTP/2 (h2c)** by default. The server uses the gRPC wire protocol via [Connect RPC](https://connectrpc.com/).
**Requirements for your plugin's gRPC server:**
- Listen on a TCP port
- Support HTTP/2 (standard for any gRPC server)
- No TLS required for local communication (h2c)
The server constructs its gRPC client with `connect.WithGRPC()`, which uses the standard gRPC binary protocol. This means your plugin can use **any** gRPC server implementation:
| Language | gRPC Library |
|----------|-------------|
| Go | `google.golang.org/grpc` or `connectrpc.com/connect` |
| C# | `Grpc.AspNetCore` |
| Python | `grpcio` |
| Java | `io.grpc` |
| Rust | `tonic` |
| Node.js | `@grpc/grpc-js` |
## Example: Go Plugin
```go
package main
import (
"context"
"log"
"net/http"
"connectrpc.com/connect"
pluginv1 "code.gitcaddy.com/server/v3/modules/plugins/pluginv1"
"code.gitcaddy.com/server/v3/modules/plugins/pluginv1/pluginv1connect"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
type myPlugin struct {
pluginv1connect.UnimplementedPluginServiceHandler
}
func (p *myPlugin) Initialize(
ctx context.Context,
req *connect.Request[pluginv1.InitializeRequest],
) (*connect.Response[pluginv1.InitializeResponse], error) {
log.Printf("Initialized by server %s", req.Msg.ServerVersion)
return connect.NewResponse(&pluginv1.InitializeResponse{
Success: true,
Manifest: &pluginv1.PluginManifest{
Name: "My Plugin",
Version: "1.0.0",
Description: "Does something useful",
SubscribedEvents: []string{"repo:push"},
Routes: []*pluginv1.PluginRoute{
{Method: "GET", Path: "/api/v1/my-plugin/status", Description: "Plugin status"},
},
},
}), nil
}
func (p *myPlugin) HealthCheck(
ctx context.Context,
req *connect.Request[pluginv1.HealthCheckRequest],
) (*connect.Response[pluginv1.HealthCheckResponse], error) {
return connect.NewResponse(&pluginv1.HealthCheckResponse{
Healthy: true,
Status: "operational",
Details: map[string]string{"version": "1.0.0"},
}), nil
}
func (p *myPlugin) Shutdown(
ctx context.Context,
req *connect.Request[pluginv1.ShutdownRequest],
) (*connect.Response[pluginv1.ShutdownResponse], error) {
log.Printf("Shutdown requested: %s", req.Msg.Reason)
return connect.NewResponse(&pluginv1.ShutdownResponse{Success: true}), nil
}
func (p *myPlugin) OnEvent(
ctx context.Context,
req *connect.Request[pluginv1.PluginEvent],
) (*connect.Response[pluginv1.EventResponse], error) {
log.Printf("Event: %s for repo %d", req.Msg.EventType, req.Msg.RepoId)
return connect.NewResponse(&pluginv1.EventResponse{Handled: true}), nil
}
func main() {
mux := http.NewServeMux()
path, handler := pluginv1connect.NewPluginServiceHandler(&myPlugin{})
mux.Handle(path, handler)
server := &http.Server{
Addr: ":9090",
Handler: h2c.NewHandler(mux, &http2.Server{}),
}
log.Println("Plugin listening on :9090")
log.Fatal(server.ListenAndServe())
}
```
**app.ini:**
```ini
[plugins.my-plugin]
ENABLED = true
ADDRESS = localhost:9090
SUBSCRIBED_EVENTS = repo:push
```
## Example: C# Plugin
```csharp
using Grpc.Core;
// Assumes plugin.proto is included in the .csproj with GrpcServices="Server"
public class MyPlugin : PluginService.PluginServiceBase
{
public override Task<InitializeResponse> Initialize(
InitializeRequest request, ServerCallContext context)
{
var manifest = new PluginManifest
{
Name = "My C# Plugin",
Version = "1.0.0",
Description = "A C# plugin for GitCaddy"
};
manifest.SubscribedEvents.Add("issue:created");
return Task.FromResult(new InitializeResponse
{
Success = true,
Manifest = manifest
});
}
public override Task<HealthCheckResponse> HealthCheck(
HealthCheckRequest request, ServerCallContext context)
{
return Task.FromResult(new HealthCheckResponse
{
Healthy = true,
Status = "operational"
});
}
public override Task<ShutdownResponse> Shutdown(
ShutdownRequest request, ServerCallContext context)
{
return Task.FromResult(new ShutdownResponse { Success = true });
}
public override Task<EventResponse> OnEvent(
PluginEvent request, ServerCallContext context)
{
Console.WriteLine($"Event: {request.EventType} for repo {request.RepoId}");
return Task.FromResult(new EventResponse { Handled = true });
}
}
```
**Program.cs:**
```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(9090, o =>
o.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http2);
});
var app = builder.Build();
app.MapGrpcService<MyPlugin>();
app.Run();
```
## Example: Python Plugin
```python
import grpc
from concurrent import futures
# Generated from plugin.proto using grpcio-tools:
# python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. plugin.proto
import plugin_pb2
import plugin_pb2_grpc
class MyPlugin(plugin_pb2_grpc.PluginServiceServicer):
def Initialize(self, request, context):
manifest = plugin_pb2.PluginManifest(
name="My Python Plugin",
version="1.0.0",
description="A Python plugin for GitCaddy",
subscribed_events=["repo:push"],
)
return plugin_pb2.InitializeResponse(success=True, manifest=manifest)
def HealthCheck(self, request, context):
return plugin_pb2.HealthCheckResponse(
healthy=True,
status="operational",
details={"version": "1.0.0"},
)
def Shutdown(self, request, context):
print(f"Shutdown: {request.reason}")
return plugin_pb2.ShutdownResponse(success=True)
def OnEvent(self, request, context):
print(f"Event: {request.event_type} for repo {request.repo_id}")
return plugin_pb2.EventResponse(handled=True)
def HandleHTTP(self, request, context):
return plugin_pb2.HTTPResponse(status_code=501)
def GetManifest(self, request, context):
return plugin_pb2.PluginManifest(
name="My Python Plugin",
version="1.0.0",
)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
plugin_pb2_grpc.add_PluginServiceServicer_to_server(MyPlugin(), server)
server.add_insecure_port("[::]:9090")
server.start()
print("Plugin listening on :9090")
server.wait_for_termination()
if __name__ == "__main__":
serve()
```
## Debugging
**Server logs** show plugin lifecycle events:
```
[I] Loaded external plugin config: my-plugin (managed=false)
[I] External plugin my-plugin is online (managed=false)
[W] Health check failed for plugin my-plugin: connection refused
[E] Plugin my-plugin is now offline after 3 consecutive health check failures
[I] Plugin my-plugin is back online
[I] Shutting down external plugin: my-plugin
```
**Tips:**
- Use `HEALTH_TIMEOUT = 10s` during development to avoid false positives
- Set `HEALTH_CHECK_INTERVAL = 5s` for faster feedback during testing
- Check that your plugin supports cleartext HTTP/2 (h2c) - this is the most common connection issue
- Use `grpcurl` to test your plugin's gRPC service independently:
```bash
# List services
grpcurl -plaintext localhost:9090 list
# Call HealthCheck
grpcurl -plaintext localhost:9090 plugin.v1.PluginService/HealthCheck
# Call Initialize
grpcurl -plaintext -d '{"server_version": "3.0.0"}' \
localhost:9090 plugin.v1.PluginService/Initialize
```

View File

@@ -21,6 +21,7 @@ The AI-native Git platform. Self-hosted, fast, and designed for the age of AI-as
- [Tier 2 - Agent Operations](#ai-powered-features)
- [Configuration Cascade](#ai-powered-features)
- [Built-in Safety](#ai-powered-features)
- [Plugin Framework](#plugin-framework)
- [Landing Pages & Public Releases](#landing-pages--public-releases)
- [App Update API (Electron/Squirrel Compatible)](#app-update-api-electronsquirrel-compatible)
- [Release Archive](#release-archive)
@@ -34,6 +35,7 @@ The AI-native Git platform. Self-hosted, fast, and designed for the age of AI-as
- [Configuration](#configuration)
- [Database Setup](#database-setup)
- [AI Features Configuration](#ai-features-configuration)
- [Plugin Framework Configuration](#plugin-framework-configuration)
- [V2 API Configuration](#v2-api-configuration)
- [Authentication Sources](#authentication-sources)
- [Email/SMTP Setup](#emailsmtp-setup)
@@ -297,6 +299,19 @@ Settings resolve from most specific to least: Repo > Org > System. Each level ca
- Escalation to staff when AI confidence is low or operations fail
- Full audit log of every AI operation with provider, tokens, duration, and status
### Plugin Framework
Extend GitCaddy with external plugins that communicate over gRPC (HTTP/2). The server manages plugin lifecycle, health monitoring, and event dispatch.
- **gRPC Protocol**: Type-safe plugin contract defined in `plugin.proto` with 6 RPCs (Initialize, Shutdown, HealthCheck, GetManifest, OnEvent, HandleHTTP)
- **Health Monitoring**: Automatic periodic health checks with configurable interval; plugins marked offline after 3 consecutive failures
- **Auto-Restart**: Managed plugins are automatically restarted when they become unresponsive
- **Event Dispatch**: Plugins subscribe to server events (e.g., `license:updated`) and receive them in real-time
- **Route Declaration**: Plugins declare REST routes in their manifest; the server can proxy HTTP requests to the plugin
- **Managed & External Modes**: Server can launch and manage plugin processes, or connect to already-running services
See [PLUGINS.md](PLUGINS.md) for the full plugin development guide.
### Landing Pages & Public Releases
Customizable repository landing pages with optional public access for private repositories:
@@ -608,6 +623,58 @@ For agent mode (AI that creates branches and PRs), you need an Actions runner wi
When a trigger label is added to an issue (or an agent fix is requested via API), the server dispatches the workflow, the runner clones the repo, runs Claude Code headless, and creates a PR with the proposed fix.
### Plugin Framework Configuration
The plugin framework allows external services (like the AI sidecar) to register with GitCaddy for lifecycle management, health monitoring, and event dispatch. Plugins communicate over gRPC (HTTP/2) using the protocol defined in `modules/plugins/pluginv1/plugin.proto`.
**1. Enable the plugin framework in `app.ini`:**
```ini
[plugins]
; Master switch for the plugin framework
ENABLED = true
; Directory for plugin data
PATH = data/plugins
; How often to health-check external plugins
HEALTH_CHECK_INTERVAL = 30s
```
**2. Register external plugins:**
Each plugin gets its own `[plugins.<name>]` section:
```ini
[plugins.gitcaddy-ai]
ENABLED = true
; Address of the plugin's gRPC endpoint (cleartext HTTP/2)
ADDRESS = localhost:5000
; Health check timeout
HEALTH_TIMEOUT = 5s
; Events the plugin subscribes to (comma-separated)
SUBSCRIBED_EVENTS = license:updated
```
**Managed vs External Mode:**
| Mode | Configuration | Use Case |
|------|--------------|----------|
| **External** | `ADDRESS` only | Plugin runs independently (Docker, systemd, sidecar) |
| **Managed** | `BINARY` + `ARGS` | Server launches and manages the plugin process |
Managed mode example:
```ini
[plugins.my-plugin]
ENABLED = true
BINARY = /usr/local/bin/my-plugin
ARGS = --port 5001
ADDRESS = localhost:5001
HEALTH_TIMEOUT = 5s
```
When a managed plugin becomes unresponsive (3 consecutive health check failures), the server automatically restarts its process.
See [PLUGINS.md](PLUGINS.md) for the full plugin development guide.
### V2 API Configuration
```ini

View File

@@ -1,206 +0,0 @@
# Gitea
[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [繁體中文](./README.zh-tw.md)
## 目的
这个项目的目标是提供最简单、最快速、最无痛的方式来设置自托管的 Git 服务。
由于 Gitea 是用 Go 语言编写的,它可以在 Go 支持的所有平台和架构上运行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架构。这个项目自 2016 年 11 月从 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而来,但已经有了很多变化。
在线演示可以访问 [demo.gitea.com](https://demo.gitea.com)。
要访问免费的 Gitea 服务(有一定数量的仓库限制),可以访问 [gitea.com](https://gitea.com/user/login)。
要快速部署您自己的专用 Gitea 实例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
## 文件
您可以在我们的官方 [文件网站](https://docs.gitea.com/) 上找到全面的文件。
它包括安装、管理、使用、开发、贡献指南等,帮助您快速入门并有效地探索所有功能。
如果您有任何建议或想要贡献,可以访问 [文件仓库](https://gitea.com/gitea/docs)
## 构建
从源代码树的根目录运行:
TAGS="bindata" make build
如果需要 SQLite 支持:
TAGS="bindata sqlite sqlite_unlock_notify" make build
`build` 目标分为两个子目标:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。
更多信息https://docs.gitea.com/installation/install-from-source
## 使用
构建后,默认情况下会在源代码树的根目录生成一个名为 `gitea` 的二进制文件。要运行它,请使用:
./gitea web
> [!注意]
> 如果您对使用我们的 API 感兴趣,我们提供了实验性支持,并附有 [文件](https://docs.gitea.com/api)。
## 贡献
预期的工作流程是Fork -> Patch -> Push -> Pull Request
> [!注意]
>
> 1. **在开始进行 Pull Request 之前,您必须阅读 [贡献者指南](CONTRIBUTING.md)。**
> 2. 如果您在项目中发现了漏洞,请私下写信给 **security@gitea.io**。谢谢!
## 翻译
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com)
翻译通过 [Crowdin](https://translate.gitea.com) 进行。如果您想翻译成新的语言,请在 Crowdin 项目中请求管理员添加新语言。
您也可以创建一个 issue 来添加语言,或者在 discord 的 #translation 频道上询问。如果您需要上下文或发现一些翻译问题,可以在字符串上留言或在 Discord 上询问。对于一般的翻译问题,文档中有一个部分。目前有点空,但我们希望随着问题的出现而填充它。
更多信息请参阅 [文件](https://docs.gitea.com/contributing/localization)。
## 官方和第三方项目
我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。
我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。
## 通讯
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
如果您有任何文件未涵盖的问题,可以在我们的 [Discord 服务器](https://discord.gg/Gitea) 上与我们联系,或者在 [discourse 论坛](https://forum.gitea.com/) 上创建帖子。
## 作者
- [维护者](https://github.com/orgs/go-gitea/people)
- [贡献者](https://github.com/go-gitea/gitea/graphs/contributors)
- [翻译者](options/locale/TRANSLATORS)
## 支持者
感谢所有支持者! 🙏 [[成为支持者](https://opencollective.com/gitea#backer)]
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
## 赞助商
通过成为赞助商来支持这个项目。您的标志将显示在这里,并带有链接到您的网站。 [[成为赞助商](https://opencollective.com/gitea#sponsor)]
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
## 常见问题
**Gitea 怎么发音?**
Gitea 的发音是 [/ɡɪti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一样g 是硬音。
**为什么这个项目没有托管在 Gitea 实例上?**
我们正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。
**在哪里可以找到安全补丁?**
在 [发布日志](https://github.com/go-gitea/gitea/releases) 或 [变更日志](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索关键词 `SECURITY` 以找到安全补丁。
## 许可证
这个项目是根据 MIT 许可证授权的。
请参阅 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以获取完整的许可证文本。
## 进一步信息
<details>
<summary>寻找界面概述?查看这里!</summary>
### 登录/注册页面
![Login](https://dl.gitea.com/screenshots/login.png)
![Register](https://dl.gitea.com/screenshots/register.png)
### 用户仪表板
![Home](https://dl.gitea.com/screenshots/home.png)
![Issues](https://dl.gitea.com/screenshots/issues.png)
![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png)
![Milestones](https://dl.gitea.com/screenshots/milestones.png)
### 用户资料
![Profile](https://dl.gitea.com/screenshots/user_profile.png)
### 探索
![Repos](https://dl.gitea.com/screenshots/explore_repos.png)
![Users](https://dl.gitea.com/screenshots/explore_users.png)
![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png)
### 仓库
![Home](https://dl.gitea.com/screenshots/repo_home.png)
![Commits](https://dl.gitea.com/screenshots/repo_commits.png)
![Branches](https://dl.gitea.com/screenshots/repo_branches.png)
![Labels](https://dl.gitea.com/screenshots/repo_labels.png)
![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png)
![Releases](https://dl.gitea.com/screenshots/repo_releases.png)
![Tags](https://dl.gitea.com/screenshots/repo_tags.png)
#### 仓库问题
![List](https://dl.gitea.com/screenshots/repo_issues.png)
![Issue](https://dl.gitea.com/screenshots/repo_issue.png)
#### 仓库拉取请求
![List](https://dl.gitea.com/screenshots/repo_pull_requests.png)
![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png)
![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png)
![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png)
#### 仓库操作
![List](https://dl.gitea.com/screenshots/repo_actions.png)
![Details](https://dl.gitea.com/screenshots/repo_actions_run.png)
#### 仓库活动
![Activity](https://dl.gitea.com/screenshots/repo_activity.png)
![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png)
![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png)
![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png)
### 组织
![Home](https://dl.gitea.com/screenshots/org_home.png)
</details>

View File

@@ -1,206 +0,0 @@
# Gitea
[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [简体中文](./README.zh-cn.md)
## 目的
這個項目的目標是提供最簡單、最快速、最無痛的方式來設置自託管的 Git 服務。
由於 Gitea 是用 Go 語言編寫的,它可以在 Go 支援的所有平台和架構上運行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架構。這個項目自 2016 年 11 月從 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而來,但已經有了很多變化。
在線演示可以訪問 [demo.gitea.com](https://demo.gitea.com)。
要訪問免費的 Gitea 服務(有一定數量的倉庫限制),可以訪問 [gitea.com](https://gitea.com/user/login)。
要快速部署您自己的專用 Gitea 實例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 開始免費試用。
## 文件
您可以在我們的官方 [文件網站](https://docs.gitea.com/) 上找到全面的文件。
它包括安裝、管理、使用、開發、貢獻指南等,幫助您快速入門並有效地探索所有功能。
如果您有任何建議或想要貢獻,可以訪問 [文件倉庫](https://gitea.com/gitea/docs)
## 構建
從源代碼樹的根目錄運行:
TAGS="bindata" make build
如果需要 SQLite 支援:
TAGS="bindata sqlite sqlite_unlock_notify" make build
`build` 目標分為兩個子目標:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。
更多信息https://docs.gitea.com/installation/install-from-source
## 使用
構建後,默認情況下會在源代碼樹的根目錄生成一個名為 `gitea` 的二進制文件。要運行它,請使用:
./gitea web
> [!注意]
> 如果您對使用我們的 API 感興趣,我們提供了實驗性支援,並附有 [文件](https://docs.gitea.com/api)。
## 貢獻
預期的工作流程是Fork -> Patch -> Push -> Pull Request
> [!注意]
>
> 1. **在開始進行 Pull Request 之前,您必須閱讀 [貢獻者指南](CONTRIBUTING.md)。**
> 2. 如果您在項目中發現了漏洞,請私下寫信給 **security@gitea.io**。謝謝!
## 翻譯
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com)
翻譯通過 [Crowdin](https://translate.gitea.com) 進行。如果您想翻譯成新的語言,請在 Crowdin 項目中請求管理員添加新語言。
您也可以創建一個 issue 來添加語言,或者在 discord 的 #translation 頻道上詢問。如果您需要上下文或發現一些翻譯問題,可以在字符串上留言或在 Discord 上詢問。對於一般的翻譯問題,文檔中有一個部分。目前有點空,但我們希望隨著問題的出現而填充它。
更多信息請參閱 [文件](https://docs.gitea.com/contributing/localization)。
## 官方和第三方項目
我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。
我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。
## 通訊
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
如果您有任何文件未涵蓋的問題,可以在我們的 [Discord 服務器](https://discord.gg/Gitea) 上與我們聯繫,或者在 [discourse 論壇](https://forum.gitea.com/) 上創建帖子。
## 作者
- [維護者](https://github.com/orgs/go-gitea/people)
- [貢獻者](https://github.com/go-gitea/gitea/graphs/contributors)
- [翻譯者](options/locale/TRANSLATORS)
## 支持者
感謝所有支持者! 🙏 [[成為支持者](https://opencollective.com/gitea#backer)]
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
## 贊助商
通過成為贊助商來支持這個項目。您的標誌將顯示在這裡,並帶有鏈接到您的網站。 [[成為贊助商](https://opencollective.com/gitea#sponsor)]
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
## 常見問題
**Gitea 怎麼發音?**
Gitea 的發音是 [/ɡɪti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一樣g 是硬音。
**為什麼這個項目沒有託管在 Gitea 實例上?**
我們正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。
**在哪裡可以找到安全補丁?**
在 [發佈日誌](https://github.com/go-gitea/gitea/releases) 或 [變更日誌](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索關鍵詞 `SECURITY` 以找到安全補丁。
## 許可證
這個項目是根據 MIT 許可證授權的。
請參閱 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以獲取完整的許可證文本。
## 進一步信息
<details>
<summary>尋找界面概述?查看這裡!</summary>
### 登錄/註冊頁面
![Login](https://dl.gitea.com/screenshots/login.png)
![Register](https://dl.gitea.com/screenshots/register.png)
### 用戶儀表板
![Home](https://dl.gitea.com/screenshots/home.png)
![Issues](https://dl.gitea.com/screenshots/issues.png)
![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png)
![Milestones](https://dl.gitea.com/screenshots/milestones.png)
### 用戶資料
![Profile](https://dl.gitea.com/screenshots/user_profile.png)
### 探索
![Repos](https://dl.gitea.com/screenshots/explore_repos.png)
![Users](https://dl.gitea.com/screenshots/explore_users.png)
![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png)
### 倉庫
![Home](https://dl.gitea.com/screenshots/repo_home.png)
![Commits](https://dl.gitea.com/screenshots/repo_commits.png)
![Branches](https://dl.gitea.com/screenshots/repo_branches.png)
![Labels](https://dl.gitea.com/screenshots/repo_labels.png)
![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png)
![Releases](https://dl.gitea.com/screenshots/repo_releases.png)
![Tags](https://dl.gitea.com/screenshots/repo_tags.png)
#### 倉庫問題
![List](https://dl.gitea.com/screenshots/repo_issues.png)
![Issue](https://dl.gitea.com/screenshots/repo_issue.png)
#### 倉庫拉取請求
![List](https://dl.gitea.com/screenshots/repo_pull_requests.png)
![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png)
![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png)
![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png)
#### 倉庫操作
![List](https://dl.gitea.com/screenshots/repo_actions.png)
![Details](https://dl.gitea.com/screenshots/repo_actions_run.png)
#### 倉庫活動
![Activity](https://dl.gitea.com/screenshots/repo_activity.png)
![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png)
![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png)
![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png)
### 組織
![Home](https://dl.gitea.com/screenshots/org_home.png)
</details>