Creating a custom integration
This guide shows how to create a custom Plakar integration in Go, build it,
package it, and install it using the plakar CLI.
An integration is a package of one or more connectors. An importer connector reads data from a source during backup, an exporter connector writes it to a destination during restore, and a storage connector hosts a repository.
Why write a custom integration?
Plakar ships with integrations for common sources and storage backends. When you need to back up something that isn’t supported out of the box, such as an internal database or a custom data source, you can write your own integration in Go and install it like any other package.
What you will build
An integration with a single importer connector that backs up one hardcoded file. Once you understand the pattern, you can extend it to walk directories, read from APIs, or consume any other data source.
The guide follows the layout of the example integration, which also implements an exporter and a storage backend.
Prerequisites
- Go 1.25 or later
plakarinstalled and available in your$PATH
Set up the project
Create a new Go module for your integration:
mkdir plakar-example
cd plakar-example
go mod init github.com/yourorg/plakar-exampleInstall the two required dependencies:
go get github.com/PlakarKorp/go-kloset-sdk
go get github.com/PlakarKorp/klosetCreate the project structure:
.
├── connector.go # Connector implementation
├── cmd/
│ └── importer/
│ └── main.go # Importer entry point
├── manifest.yaml # Manifest describing the integration
├── Makefile # Build and packaging targets
├── go.mod
└── go.sumImplement the connector
Create connector.go:
package connector
import (
"context"
"io"
"os"
"path/filepath"
"github.com/PlakarKorp/kloset/connectors"
"github.com/PlakarKorp/kloset/location"
"github.com/PlakarKorp/kloset/connectors/importer"
"github.com/PlakarKorp/kloset/objects"
)
const FILE = "/home/user/Documents/notes.md"
type testConnector struct{}
func NewImporter(ctx context.Context, opts *connectors.Options, proto string, config map[string]string) (importer.Importer, error) {
return &testConnector{}, nil
}
func (f *testConnector) Root() string { return filepath.Dir(FILE) }
func (f *testConnector) Origin() string { return "localhost" }
func (f *testConnector) Type() string { return "example" }
func (f *testConnector) Flags() location.Flags { return location.FLAG_LOCALFS }
func (f *testConnector) Ping(_ context.Context) error { return nil }
func (f *testConnector) Close(_ context.Context) error { return nil }
func (f *testConnector) Import(ctx context.Context, records chan<- *connectors.Record, results <-chan *connectors.Result) error {
defer close(records)
info, err := os.Stat(FILE)
if err != nil {
records <- connectors.NewError(FILE, err)
return nil
}
fi := objects.FileInfo{
Lname: filepath.Base(FILE),
Lsize: info.Size(),
Lmode: info.Mode(),
LmodTime: info.ModTime(),
}
records <- connectors.NewRecord(FILE, "", fi, nil, func() (io.ReadCloser, error) {
return os.Open(FILE)
})
return nil
}FLAG_LOCALFS tells Plakar the location is a local filesystem path. A file that
cannot be read is reported with connectors.NewError() rather than failing the
whole backup.
Writing to the console
Never write to os.Stdout. Plakar communicates with the connector over gRPC
through stdin and stdout, so any writes there corrupt the stream. Use
os.Stderr for debug output instead.
Create the entry point
Create cmd/importer/main.go:
package main
import (
"context"
"os"
sdk "github.com/PlakarKorp/go-kloset-sdk"
connector "github.com/yourorg/plakar-example"
)
func main() {
ctx := sdk.InterruptableContext(context.Background())
sdk.EntrypointImporter(ctx, os.Args, connector.NewImporter)
}InterruptableContext cancels the context when the process receives an
interrupt, so the connector stops cleanly.
Write the manifest
Create manifest.yaml:
name: example
description: A minimal integration that backs up a single file.
homepage: https://github.com/yourorg/plakar-example
license: ISC
connectors:
- type: importer
executable: ./example-importer
protocols: [example]
location_flags: [localfs]The manifest describes the integration and each connector it contains. The
executable value must match the binary produced in the build step. The
location_flags list must match the flags returned by the connector’s Flags()
method.
See the manifest reference for the optional fields.
Build the integration
Create a Makefile:
GO = go
PLAKAR = plakar
VERSION = v1.0.0
all: build
build:
${GO} build -o example-importer ./cmd/importer
package: build
rm -f example_${VERSION}_*.ptar
${PLAKAR} pkg create ./manifest.yaml ${VERSION}
uninstall:
-${PLAKAR} pkg rm example
install: package
${PLAKAR} pkg add -allow-unsigned ./example_${VERSION}_*.ptar
reinstall: uninstall install
clean:
rm -f example-importer example_${VERSION}*.ptar
.PHONY: all build package uninstall install reinstall cleanPackage and install
Package and install the integration in one step:
make installThis builds the binary, runs plakar pkg create to produce
example_v1.0.0_<os>_<arch>.ptar, and installs it with plakar pkg add. A
local build is unsigned, so the install target passes -allow-unsigned.
To update an existing installation:
make reinstallThis removes the previously installed package with plakar pkg rm before
installing the new one.
Verify the installation:
plakar pkg showYou should see example listed.
Use the integration
Back up using your new importer:
plakar at /var/backups backup example://Because this importer uses a hardcoded file path, the location after
example:// is ignored. The importer always reads from
/home/user/Documents/notes.md.
Next steps
Walking a directory. Instead of a hardcoded path, parse the location from
the config map (strings.TrimPrefix(config["location"], proto+"://")) and use
filepath.WalkDir to send a record for each file.
Remote sources. For connectors that talk to an API, use 0 as the flags
value instead of location.FLAG_LOCALFS, and parse credentials and endpoints
from the config map passed to your constructor.
Streaming imports. If your source cannot be replayed, such as a pipe, add
location.FLAG_STREAM to your flags. Plakar then calls Import only once.
Adding exporter or storage connectors. Implement the Exporter or Store
interface, add an entry point under cmd/, and add a connector entry to
manifest.yaml.
See the SDK reference for the full interface definitions and the example integration for a working importer, exporter, and storage backend.