chore: clean .gitignore cache
This commit is contained in:
@@ -1,827 +0,0 @@
|
||||
# DNS Provider Plugin Development
|
||||
|
||||
This guide covers the technical details of developing custom DNS provider plugins for Charon.
|
||||
|
||||
## Overview
|
||||
|
||||
Charon uses Go's plugin system to dynamically load DNS provider implementations. Plugins implement the `ProviderPlugin` interface and are compiled as shared libraries (`.so` files).
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Charon Core Process │
|
||||
│ ┌───────────────────────────────────┐ │
|
||||
│ │ Global Provider Registry │ │
|
||||
│ ├───────────────────────────────────┤ │
|
||||
│ │ Built-in Providers │ │
|
||||
│ │ - Cloudflare │ │
|
||||
│ │ - DNSimple │ │
|
||||
│ │ - Route53 │ │
|
||||
│ ├───────────────────────────────────┤ │
|
||||
│ │ External Plugins (*.so) │ │
|
||||
│ │ - PowerDNS [loaded] │ │
|
||||
│ │ - Custom [loaded] │ │
|
||||
│ └───────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
- **Linux:** x86_64, ARM64 (primary target)
|
||||
- **macOS:** x86_64, ARM64 (development/testing)
|
||||
- **Windows:** Not supported (Go plugin limitation)
|
||||
|
||||
### Build Requirements
|
||||
|
||||
- **CGO:** Must be enabled (`CGO_ENABLED=1`)
|
||||
- **Go Version:** Must match Charon's Go version exactly (currently 1.25.6+)
|
||||
- **Compiler:** GCC/Clang for Linux, Xcode tools for macOS
|
||||
- **Build Mode:** Must use `-buildmode=plugin`
|
||||
|
||||
## Interface Specification
|
||||
|
||||
### Interface Version
|
||||
|
||||
Current interface version: **v1**
|
||||
|
||||
The interface version is defined in `backend/pkg/dnsprovider/plugin.go`:
|
||||
|
||||
```go
|
||||
const InterfaceVersion = "v1"
|
||||
```
|
||||
|
||||
### Core Interface
|
||||
|
||||
All plugins must implement `dnsprovider.ProviderPlugin`:
|
||||
|
||||
```go
|
||||
type ProviderPlugin interface {
|
||||
Type() string
|
||||
Metadata() ProviderMetadata
|
||||
Init() error
|
||||
Cleanup() error
|
||||
RequiredCredentialFields() []CredentialFieldSpec
|
||||
OptionalCredentialFields() []CredentialFieldSpec
|
||||
ValidateCredentials(creds map[string]string) error
|
||||
TestCredentials(creds map[string]string) error
|
||||
SupportsMultiCredential() bool
|
||||
BuildCaddyConfig(creds map[string]string) map[string]any
|
||||
BuildCaddyConfigForZone(baseDomain string, creds map[string]string) map[string]any
|
||||
PropagationTimeout() time.Duration
|
||||
PollingInterval() time.Duration
|
||||
}
|
||||
```
|
||||
|
||||
### Method Reference
|
||||
|
||||
#### `Type() string`
|
||||
|
||||
Returns the unique provider identifier.
|
||||
|
||||
- Must be lowercase, alphanumeric with optional underscores
|
||||
- Used as the key for registration and lookup
|
||||
- Examples: `"powerdns"`, `"custom_dns"`, `"acme_dns"`
|
||||
|
||||
#### `Metadata() ProviderMetadata`
|
||||
|
||||
Returns descriptive information for UI display:
|
||||
|
||||
```go
|
||||
type ProviderMetadata struct {
|
||||
Type string `json:"type"` // Same as Type()
|
||||
Name string `json:"name"` // Display name
|
||||
Description string `json:"description"` // Brief description
|
||||
DocumentationURL string `json:"documentation_url"` // Help link
|
||||
Author string `json:"author"` // Plugin author
|
||||
Version string `json:"version"` // Plugin version
|
||||
IsBuiltIn bool `json:"is_built_in"` // Always false for plugins
|
||||
GoVersion string `json:"go_version"` // Build Go version
|
||||
InterfaceVersion string `json:"interface_version"` // Plugin interface version
|
||||
}
|
||||
```
|
||||
|
||||
**Required fields:** `Type`, `Name`, `Description`, `IsBuiltIn` (false), `GoVersion`, `InterfaceVersion`
|
||||
|
||||
#### `Init() error`
|
||||
|
||||
Called after the plugin is loaded, before registration.
|
||||
|
||||
Use for:
|
||||
|
||||
- Loading configuration files
|
||||
- Validating environment
|
||||
- Establishing persistent connections
|
||||
- Resource allocation
|
||||
|
||||
Return an error to prevent registration.
|
||||
|
||||
#### `Cleanup() error`
|
||||
|
||||
Called before the plugin is unregistered (graceful shutdown).
|
||||
|
||||
Use for:
|
||||
|
||||
- Closing connections
|
||||
- Flushing caches
|
||||
- Releasing resources
|
||||
|
||||
**Note:** Due to Go runtime limitations, plugin code remains in memory after `Cleanup()`.
|
||||
|
||||
#### `RequiredCredentialFields() []CredentialFieldSpec`
|
||||
|
||||
Returns credential fields that must be provided.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
return []dnsprovider.CredentialFieldSpec{
|
||||
{
|
||||
Name: "api_token",
|
||||
Label: "API Token",
|
||||
Type: "password",
|
||||
Placeholder: "Enter your API token",
|
||||
Hint: "Found in your account settings",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### `OptionalCredentialFields() []CredentialFieldSpec`
|
||||
|
||||
Returns credential fields that may be provided.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
return []dnsprovider.CredentialFieldSpec{
|
||||
{
|
||||
Name: "timeout",
|
||||
Label: "Timeout (seconds)",
|
||||
Type: "text",
|
||||
Placeholder: "30",
|
||||
Hint: "API request timeout",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### `ValidateCredentials(creds map[string]string) error`
|
||||
|
||||
Validates credential format and presence (no network calls).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
func (p *PowerDNSProvider) ValidateCredentials(creds map[string]string) error {
|
||||
if creds["api_url"] == "" {
|
||||
return fmt.Errorf("api_url is required")
|
||||
}
|
||||
if creds["api_key"] == "" {
|
||||
return fmt.Errorf("api_key is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### `TestCredentials(creds map[string]string) error`
|
||||
|
||||
Verifies credentials work with the provider API (may make network calls).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
func (p *PowerDNSProvider) TestCredentials(creds map[string]string) error {
|
||||
if err := p.ValidateCredentials(creds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Test API connectivity
|
||||
url := creds["api_url"] + "/api/v1/servers"
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("X-API-Key", creds["api_key"])
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("API connection failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("API returned status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### `SupportsMultiCredential() bool`
|
||||
|
||||
Indicates if the provider supports zone-specific credentials (Phase 3 feature).
|
||||
|
||||
Return `false` for most implementations:
|
||||
|
||||
```go
|
||||
func (p *PowerDNSProvider) SupportsMultiCredential() bool {
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
#### `BuildCaddyConfig(creds map[string]string) map[string]any`
|
||||
|
||||
Constructs Caddy DNS challenge configuration.
|
||||
|
||||
The returned map is embedded into Caddy's TLS automation policy for ACME DNS-01 challenges.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
func (p *PowerDNSProvider) BuildCaddyConfig(creds map[string]string) map[string]any {
|
||||
return map[string]any{
|
||||
"name": "powerdns",
|
||||
"api_url": creds["api_url"],
|
||||
"api_key": creds["api_key"],
|
||||
"server_id": creds["server_id"],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Caddy Configuration Reference:** See [Caddy DNS Providers](https://github.com/caddy-dns)
|
||||
|
||||
#### `BuildCaddyConfigForZone(baseDomain string, creds map[string]string) map[string]any`
|
||||
|
||||
Constructs zone-specific configuration (multi-credential mode).
|
||||
|
||||
Only called if `SupportsMultiCredential()` returns `true`.
|
||||
|
||||
Most plugins can simply delegate to `BuildCaddyConfig()`:
|
||||
|
||||
```go
|
||||
func (p *PowerDNSProvider) BuildCaddyConfigForZone(baseDomain string, creds map[string]string) map[string]any {
|
||||
return p.BuildCaddyConfig(creds)
|
||||
}
|
||||
```
|
||||
|
||||
#### `PropagationTimeout() time.Duration`
|
||||
|
||||
Returns the recommended DNS propagation wait time.
|
||||
|
||||
Typical values:
|
||||
|
||||
- **Fast providers:** 30-60 seconds (Cloudflare, PowerDNS)
|
||||
- **Standard providers:** 60-120 seconds (DNSimple, Route53)
|
||||
- **Slow providers:** 120-300 seconds (traditional DNS)
|
||||
|
||||
```go
|
||||
func (p *PowerDNSProvider) PropagationTimeout() time.Duration {
|
||||
return 60 * time.Second
|
||||
}
|
||||
```
|
||||
|
||||
#### `PollingInterval() time.Duration`
|
||||
|
||||
Returns the recommended polling interval for DNS verification.
|
||||
|
||||
Typical values: 2-10 seconds
|
||||
|
||||
```go
|
||||
func (p *PowerDNSProvider) PollingInterval() time.Duration {
|
||||
return 2 * time.Second
|
||||
}
|
||||
```
|
||||
|
||||
## Plugin Structure
|
||||
|
||||
### Minimal Plugin Template
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Wikid82/charon/backend/pkg/dnsprovider"
|
||||
)
|
||||
|
||||
// Plugin is the exported symbol that Charon looks for
|
||||
var Plugin dnsprovider.ProviderPlugin = &MyProvider{}
|
||||
|
||||
type MyProvider struct{}
|
||||
|
||||
func (p *MyProvider) Type() string {
|
||||
return "myprovider"
|
||||
}
|
||||
|
||||
func (p *MyProvider) Metadata() dnsprovider.ProviderMetadata {
|
||||
return dnsprovider.ProviderMetadata{
|
||||
Type: "myprovider",
|
||||
Name: "My DNS Provider",
|
||||
Description: "Custom DNS provider implementation",
|
||||
DocumentationURL: "https://example.com/docs",
|
||||
Author: "Your Name",
|
||||
Version: "1.0.0",
|
||||
IsBuiltIn: false,
|
||||
GoVersion: runtime.Version(),
|
||||
InterfaceVersion: dnsprovider.InterfaceVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MyProvider) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MyProvider) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MyProvider) RequiredCredentialFields() []dnsprovider.CredentialFieldSpec {
|
||||
return []dnsprovider.CredentialFieldSpec{
|
||||
{
|
||||
Name: "api_key",
|
||||
Label: "API Key",
|
||||
Type: "password",
|
||||
Placeholder: "Enter your API key",
|
||||
Hint: "Found in your account settings",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MyProvider) OptionalCredentialFields() []dnsprovider.CredentialFieldSpec {
|
||||
return []dnsprovider.CredentialFieldSpec{}
|
||||
}
|
||||
|
||||
func (p *MyProvider) ValidateCredentials(creds map[string]string) error {
|
||||
if creds["api_key"] == "" {
|
||||
return fmt.Errorf("api_key is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MyProvider) TestCredentials(creds map[string]string) error {
|
||||
return p.ValidateCredentials(creds)
|
||||
}
|
||||
|
||||
func (p *MyProvider) SupportsMultiCredential() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *MyProvider) BuildCaddyConfig(creds map[string]string) map[string]any {
|
||||
return map[string]any{
|
||||
"name": "myprovider",
|
||||
"api_key": creds["api_key"],
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MyProvider) BuildCaddyConfigForZone(baseDomain string, creds map[string]string) map[string]any {
|
||||
return p.BuildCaddyConfig(creds)
|
||||
}
|
||||
|
||||
func (p *MyProvider) PropagationTimeout() time.Duration {
|
||||
return 60 * time.Second
|
||||
}
|
||||
|
||||
func (p *MyProvider) PollingInterval() time.Duration {
|
||||
return 5 * time.Second
|
||||
}
|
||||
|
||||
func main() {}
|
||||
```
|
||||
|
||||
### Project Layout
|
||||
|
||||
```
|
||||
my-provider-plugin/
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── main.go
|
||||
├── Makefile
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### `go.mod` Requirements
|
||||
|
||||
```go
|
||||
module github.com/yourname/charon-plugin-myprovider
|
||||
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/Wikid82/charon v0.0.0-20240101000000-abcdef123456
|
||||
)
|
||||
```
|
||||
|
||||
**Important:** Use `replace` directive for local development:
|
||||
|
||||
```go
|
||||
replace github.com/Wikid82/charon => /path/to/charon
|
||||
```
|
||||
|
||||
## Building Plugins
|
||||
|
||||
### Build Command
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=1 go build -buildmode=plugin -o myprovider.so main.go
|
||||
```
|
||||
|
||||
### Build Requirements
|
||||
|
||||
1. **CGO must be enabled:**
|
||||
|
||||
```bash
|
||||
export CGO_ENABLED=1
|
||||
```
|
||||
|
||||
2. **Go version must match Charon:**
|
||||
|
||||
```bash
|
||||
go version
|
||||
# Must match Charon's build Go version
|
||||
```
|
||||
|
||||
3. **Architecture must match:**
|
||||
|
||||
```bash
|
||||
# For cross-compilation
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -buildmode=plugin
|
||||
```
|
||||
|
||||
### Makefile Example
|
||||
|
||||
```makefile
|
||||
.PHONY: build clean install
|
||||
|
||||
PLUGIN_NAME = myprovider
|
||||
OUTPUT = $(PLUGIN_NAME).so
|
||||
INSTALL_DIR = /etc/charon/plugins
|
||||
|
||||
build:
|
||||
CGO_ENABLED=1 go build -buildmode=plugin -o $(OUTPUT) main.go
|
||||
|
||||
clean:
|
||||
rm -f $(OUTPUT)
|
||||
|
||||
install: build
|
||||
install -m 755 $(OUTPUT) $(INSTALL_DIR)/
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
signature:
|
||||
@echo "SHA-256 Signature:"
|
||||
@sha256sum $(OUTPUT)
|
||||
```
|
||||
|
||||
### Build Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PLUGIN_NAME="myprovider"
|
||||
GO_VERSION=$(go version | awk '{print $3}')
|
||||
CHARON_GO_VERSION="go1.25.6"
|
||||
|
||||
# Verify Go version
|
||||
if [ "$GO_VERSION" != "$CHARON_GO_VERSION" ]; then
|
||||
echo "Warning: Go version mismatch"
|
||||
echo " Plugin: $GO_VERSION"
|
||||
echo " Charon: $CHARON_GO_VERSION"
|
||||
read -p "Continue? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build plugin
|
||||
echo "Building $PLUGIN_NAME.so..."
|
||||
CGO_ENABLED=1 go build -buildmode=plugin -o "${PLUGIN_NAME}.so" main.go
|
||||
|
||||
# Generate signature
|
||||
echo "Generating signature..."
|
||||
sha256sum "${PLUGIN_NAME}.so" | tee "${PLUGIN_NAME}.so.sha256"
|
||||
|
||||
echo "Build complete!"
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Set Up Development Environment
|
||||
|
||||
```bash
|
||||
# Clone plugin template
|
||||
git clone https://github.com/yourname/charon-plugin-template my-provider
|
||||
cd my-provider
|
||||
|
||||
# Install dependencies
|
||||
go mod download
|
||||
|
||||
# Set up local Charon dependency
|
||||
echo 'replace github.com/Wikid82/charon => /path/to/charon' >> go.mod
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
### 2. Implement Provider Interface
|
||||
|
||||
Edit `main.go` to implement all required methods.
|
||||
|
||||
### 3. Test Locally
|
||||
|
||||
```bash
|
||||
# Build plugin
|
||||
make build
|
||||
|
||||
# Copy to Charon plugin directory
|
||||
cp myprovider.so /etc/charon/plugins/
|
||||
|
||||
# Restart Charon
|
||||
systemctl restart charon
|
||||
|
||||
# Check logs
|
||||
journalctl -u charon -f | grep plugin
|
||||
```
|
||||
|
||||
### 4. Debug Plugin Loading
|
||||
|
||||
Enable debug logging in Charon:
|
||||
|
||||
```yaml
|
||||
log:
|
||||
level: debug
|
||||
```
|
||||
|
||||
Check for errors:
|
||||
|
||||
```bash
|
||||
journalctl -u charon -n 100 | grep -i plugin
|
||||
```
|
||||
|
||||
### 5. Test Credential Validation
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/admin/dns-providers/test \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "myprovider",
|
||||
"credentials": {
|
||||
"api_key": "test-key"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### 6. Test DNS Challenge
|
||||
|
||||
Configure a test domain to use your provider and request a certificate.
|
||||
|
||||
Monitor Caddy logs for DNS challenge execution:
|
||||
|
||||
```bash
|
||||
docker logs charon-caddy -f | grep dns
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
1. **Validate All Inputs:** Never trust credential data
|
||||
2. **Use HTTPS:** Always use TLS for API connections
|
||||
3. **Timeout Requests:** Set reasonable timeouts on all HTTP calls
|
||||
4. **Sanitize Errors:** Don't leak credentials in error messages
|
||||
5. **Log Safely:** Redact sensitive data from logs
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Minimize Init() Work:** Fast startup is critical
|
||||
2. **Connection Pooling:** Reuse HTTP clients and connections
|
||||
3. **Efficient Polling:** Use appropriate polling intervals
|
||||
4. **Cache When Possible:** Cache provider metadata
|
||||
5. **Fail Fast:** Return errors quickly for invalid credentials
|
||||
|
||||
### Reliability
|
||||
|
||||
1. **Handle Nil Gracefully:** Check for nil maps and slices
|
||||
2. **Provide Defaults:** Use sensible defaults for optional fields
|
||||
3. **Retry Transient Errors:** Implement exponential backoff
|
||||
4. **Graceful Degradation:** Continue working if non-critical features fail
|
||||
|
||||
### Maintainability
|
||||
|
||||
1. **Document Public APIs:** Use godoc comments
|
||||
2. **Version Your Plugin:** Include semantic versioning
|
||||
3. **Test Thoroughly:** Unit tests for all methods
|
||||
4. **Provide Examples:** Include configuration examples
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Wikid82/charon/backend/pkg/dnsprovider"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateCredentials(t *testing.T) {
|
||||
provider := &MyProvider{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
creds map[string]string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid credentials",
|
||||
creds: map[string]string{"api_key": "test-key"},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "missing api_key",
|
||||
creds: map[string]string{},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := provider.ValidateCredentials(tt.creds)
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadata(t *testing.T) {
|
||||
provider := &MyProvider{}
|
||||
meta := provider.Metadata()
|
||||
|
||||
assert.Equal(t, "myprovider", meta.Type)
|
||||
assert.NotEmpty(t, meta.Name)
|
||||
assert.False(t, meta.IsBuiltIn)
|
||||
assert.Equal(t, dnsprovider.InterfaceVersion, meta.InterfaceVersion)
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```go
|
||||
func TestRealAPIConnection(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test")
|
||||
}
|
||||
|
||||
provider := &MyProvider{}
|
||||
creds := map[string]string{
|
||||
"api_key": os.Getenv("TEST_API_KEY"),
|
||||
}
|
||||
|
||||
err := provider.TestCredentials(creds)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
```
|
||||
|
||||
Run integration tests:
|
||||
|
||||
```bash
|
||||
go test -v ./... -count=1
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Build Errors
|
||||
|
||||
#### `plugin was built with a different version of package`
|
||||
|
||||
**Cause:** Dependency version mismatch
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
go clean -cache
|
||||
go mod tidy
|
||||
go build -buildmode=plugin
|
||||
```
|
||||
|
||||
#### `cannot use -buildmode=plugin`
|
||||
|
||||
**Cause:** CGO not enabled
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
export CGO_ENABLED=1
|
||||
```
|
||||
|
||||
#### `undefined: dnsprovider.ProviderPlugin`
|
||||
|
||||
**Cause:** Missing or incorrect import
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
import "github.com/Wikid82/charon/backend/pkg/dnsprovider"
|
||||
```
|
||||
|
||||
### Runtime Errors
|
||||
|
||||
#### `plugin was built with a different version of Go`
|
||||
|
||||
**Cause:** Go version mismatch between plugin and Charon
|
||||
|
||||
**Solution:** Rebuild plugin with matching Go version
|
||||
|
||||
#### `symbol not found: Plugin`
|
||||
|
||||
**Cause:** Plugin variable not exported
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// Must be exported (capitalized)
|
||||
var Plugin dnsprovider.ProviderPlugin = &MyProvider{}
|
||||
```
|
||||
|
||||
#### `interface version mismatch`
|
||||
|
||||
**Cause:** Plugin built against incompatible interface
|
||||
|
||||
**Solution:** Update plugin to match Charon's interface version
|
||||
|
||||
## Publishing Plugins
|
||||
|
||||
### Release Checklist
|
||||
|
||||
- [ ] All methods implemented and tested
|
||||
- [ ] Go version matches current Charon release
|
||||
- [ ] Interface version set correctly
|
||||
- [ ] Documentation includes usage examples
|
||||
- [ ] README includes installation instructions
|
||||
- [ ] LICENSE file included
|
||||
- [ ] Changelog maintained
|
||||
- [ ] GitHub releases with binaries for all platforms
|
||||
|
||||
### Distribution
|
||||
|
||||
1. **GitHub Releases:**
|
||||
|
||||
```bash
|
||||
# Tag release
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
git push origin v1.0.0
|
||||
|
||||
# Build for multiple platforms
|
||||
make build-all
|
||||
|
||||
# Create GitHub release and attach binaries
|
||||
```
|
||||
|
||||
2. **Signature File:**
|
||||
|
||||
```bash
|
||||
sha256sum *.so > SHA256SUMS
|
||||
gpg --sign SHA256SUMS
|
||||
```
|
||||
|
||||
3. **Documentation:**
|
||||
- Include README with installation instructions
|
||||
- Provide configuration examples
|
||||
- List required Charon version
|
||||
- Include troubleshooting section
|
||||
|
||||
## Resources
|
||||
|
||||
### Reference Implementation
|
||||
|
||||
- **PowerDNS Plugin:** [`plugins/powerdns/main.go`](../../plugins/powerdns/main.go)
|
||||
- **Built-in Providers:** [`backend/pkg/dnsprovider/builtin/`](../../backend/pkg/dnsprovider/builtin/)
|
||||
- **Plugin Interface:** [`backend/pkg/dnsprovider/plugin.go`](../../backend/pkg/dnsprovider/plugin.go)
|
||||
|
||||
### External Documentation
|
||||
|
||||
- [Go Plugin Package](https://pkg.go.dev/plugin)
|
||||
- [Caddy DNS Providers](https://github.com/caddy-dns)
|
||||
- [ACME DNS-01 Challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
|
||||
|
||||
### Community
|
||||
|
||||
- **GitHub Discussions:** <https://github.com/Wikid82/charon/discussions>
|
||||
- **Plugin Registry:** <https://github.com/Wikid82/charon-plugins>
|
||||
- **Issue Tracker:** <https://github.com/Wikid82/charon/issues>
|
||||
|
||||
## See Also
|
||||
|
||||
- [Custom Plugin Installation Guide](../features/custom-plugins.md)
|
||||
- [DNS Provider Configuration](../features/dns-providers.md)
|
||||
- [Contributing Guidelines](../../CONTRIBUTING.md)
|
||||
Reference in New Issue
Block a user