commit 2bb91d33d489ccb303403e22313684dea48909b3 Author: Pierre-Olivier Mercier Date: Wed Apr 8 04:18:24 2026 +0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..522f394 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +checker-matrix +*.so diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..447bd68 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.25-alpine AS builder + +ARG CHECKER_VERSION=custom-build + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -ldflags "-X main.Version=${CHECKER_VERSION}" -o /checker-matrix . + +FROM scratch +COPY --from=builder /checker-matrix /checker-matrix +EXPOSE 8080 +ENTRYPOINT ["/checker-matrix"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..07d44d8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 The happyDomain Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..faf351c --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +CHECKER_NAME := checker-matrix +CHECKER_IMAGE := happydomain/$(CHECKER_NAME) +CHECKER_VERSION ?= custom-build + +CHECKER_SOURCES := main.go $(wildcard checker/*.go) + +GO_LDFLAGS := -X main.Version=$(CHECKER_VERSION) + +.PHONY: all plugin docker clean + +all: $(CHECKER_NAME) + +$(CHECKER_NAME): $(CHECKER_SOURCES) + go build -ldflags "$(GO_LDFLAGS)" -o $@ . + +plugin: $(CHECKER_NAME).so + +$(CHECKER_NAME).so: $(CHECKER_SOURCES) $(wildcard plugin/*.go) + go build -buildmode=plugin -ldflags "$(GO_LDFLAGS)" -o $@ ./plugin/ + +docker: + docker build --build-arg CHECKER_VERSION=$(CHECKER_VERSION) -t $(CHECKER_IMAGE) . + +clean: + rm -f $(CHECKER_NAME) $(CHECKER_NAME).so diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..8dab13f --- /dev/null +++ b/NOTICE @@ -0,0 +1,26 @@ +checker-matrix +Copyright (c) 2026 The happyDomain Authors + +This product is licensed under the MIT License (see LICENSE). + +------------------------------------------------------------------------------- +Third-party notices +------------------------------------------------------------------------------- + +This product includes software developed as part of the checker-sdk-go +project (https://git.happydns.org/happyDomain/checker-sdk-go), licensed +under the Apache License, Version 2.0: + + checker-sdk-go + Copyright 2020-2026 The happyDomain Authors + + This product includes software developed as part of the happyDomain + project (https://happydomain.org). + + Portions of this code were originally written for the happyDomain + server (licensed under AGPL-3.0 and a commercial license) and are + made available there under the Apache License, Version 2.0 to enable + a permissively licensed ecosystem of checker plugins. + +You may obtain a copy of the Apache License 2.0 at: + http://www.apache.org/licenses/LICENSE-2.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..7be7c78 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# checker-matrix + +Matrix federation checker for [happyDomain](https://www.happydomain.org/). + +Queries a [Matrix Federation Tester](https://federationtester.matrix.org/) +instance to verify that a Matrix homeserver is correctly federating, stores +the full report as an observation, and renders a rich HTML summary +(connections, certificates, well-known, DNS/SRV resolution). + +## Usage + +### Standalone HTTP server + +```bash +make +./checker-matrix -listen :8080 +``` + +The server exposes the standard happyDomain external checker endpoints +(`/health`, `/definition`, `/collect`, `/evaluate`, `/html-report`). + +### Docker + +```bash +make docker +docker run -p 8080:8080 happydomain/checker-matrix +``` + +### happyDomain plugin + +```bash +make plugin +# produces checker-matrix.so, loadable by happyDomain as a Go plugin +``` + +The plugin exposes a `NewCheckerPlugin` symbol returning the checker +definition and observation provider, which happyDomain registers in its +global registries at load time. + +### Versioning + +The binary, plugin, and Docker image embed a version string overridable +at build time: + +```bash +make CHECKER_VERSION=1.2.3 +make plugin CHECKER_VERSION=1.2.3 +make docker CHECKER_VERSION=1.2.3 +``` + +### happyDomain remote endpoint + +Set the `endpoint` admin option for the `matrixim` checker to the URL of +the running checker-matrix server (e.g., `http://checker-matrix:8080`). +happyDomain will delegate observation collection to this endpoint. + +## Options + +| Scope | Id | Description | +| ----- | ------------------------ | -------------------------------------------------------------------------- | +| Run | `serviceDomain` | Matrix domain to test (auto-filled, default `matrix.org`) | +| Admin | `federationTesterServer` | Federation Tester URL template (default: `https://federationtester.matrix.org/api/report?server_name=%s`) | + +The checker only applies to services of type `abstract.MatrixIM`. + +## License + +This project is licensed under the **MIT License** (see `LICENSE`). The +third-party Apache-2.0 attributions for `checker-sdk-go` are recorded in +`NOTICE` and must accompany any binary or source redistribution of this +project. diff --git a/checker/collect.go b/checker/collect.go new file mode 100644 index 0000000..9bc6340 --- /dev/null +++ b/checker/collect.go @@ -0,0 +1,46 @@ +package checker + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + sdk "git.happydns.org/checker-sdk-go/checker" +) + +func (p *matrixProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any, error) { + domain, _ := opts["serviceDomain"].(string) + if domain == "" { + return nil, fmt.Errorf("serviceDomain is required") + } + domain = strings.TrimSuffix(domain, ".") + + testerURI, _ := opts["federationTesterServer"].(string) + if testerURI == "" { + testerURI = "https://federationtester.matrix.org/api/report?server_name=%s" + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(testerURI, domain), nil) + if err != nil { + return nil, fmt.Errorf("unable to build the request: %w", err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("unable to perform the test: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 300 { + return nil, fmt.Errorf("federation tester returned status %d; check https://federationtester.matrix.org/#%s", resp.StatusCode, domain) + } + + var data MatrixFederationData + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, fmt.Errorf("failed to decode federation tester response: %w", err) + } + + return &data, nil +} diff --git a/checker/definition.go b/checker/definition.go new file mode 100644 index 0000000..4e5ec7c --- /dev/null +++ b/checker/definition.go @@ -0,0 +1,62 @@ +package checker + +import ( + "time" + + sdk "git.happydns.org/checker-sdk-go/checker" +) + +// Version is the checker version reported in CheckerDefinition.Version. +// +// It defaults to "built-in", which is appropriate when the checker package is +// imported directly (built-in or plugin mode). Standalone binaries (like +// main.go) should override this from their own Version variable at the start +// of main(), which makes it easy for CI to inject a version with a single +// -ldflags "-X main.Version=..." flag instead of targeting the nested +// package path. +var Version = "built-in" + +// Definition returns the CheckerDefinition for the matrix federation checker. +func Definition() *sdk.CheckerDefinition { + return &sdk.CheckerDefinition{ + ID: "matrixim", + Name: "Matrix Federation Tester", + Version: Version, + Availability: sdk.CheckerAvailability{ + ApplyToService: true, + LimitToServices: []string{"abstract.MatrixIM"}, + }, + HasHTMLReport: true, + ObservationKeys: []sdk.ObservationKey{ObservationKeyMatrix}, + Options: sdk.CheckerOptionsDocumentation{ + RunOpts: []sdk.CheckerOptionDocumentation{ + { + Id: "serviceDomain", + Type: "string", + Label: "Matrix domain", + Placeholder: "matrix.org", + Default: "matrix.org", + AutoFill: sdk.AutoFillDomainName, + Required: true, + }, + }, + AdminOpts: []sdk.CheckerOptionDocumentation{ + { + Id: "federationTesterServer", + Type: "string", + Label: "Federation Tester Server", + Placeholder: "https://federationtester.matrix.org/api/report?server_name=%s", + Default: "https://federationtester.matrix.org/api/report?server_name=%s", + }, + }, + }, + Rules: []sdk.CheckRule{ + Rule(), + }, + Interval: &sdk.CheckIntervalSpec{ + Min: 5 * time.Minute, + Max: 7 * 24 * time.Hour, + Default: 24 * time.Hour, + }, + } +} diff --git a/checker/provider.go b/checker/provider.go new file mode 100644 index 0000000..de7a43d --- /dev/null +++ b/checker/provider.go @@ -0,0 +1,21 @@ +package checker + +import ( + sdk "git.happydns.org/checker-sdk-go/checker" +) + +// Provider returns a new matrix federation observation provider. +func Provider() sdk.ObservationProvider { + return &matrixProvider{} +} + +type matrixProvider struct{} + +func (p *matrixProvider) Key() sdk.ObservationKey { + return ObservationKeyMatrix +} + +// Definition implements sdk.CheckerDefinitionProvider. +func (p *matrixProvider) Definition() *sdk.CheckerDefinition { + return Definition() +} diff --git a/checker/report.go b/checker/report.go new file mode 100644 index 0000000..8066ae2 --- /dev/null +++ b/checker/report.go @@ -0,0 +1,376 @@ +package checker + +import ( + "encoding/json" + "fmt" + "html/template" + "strings" +) + +// ── HTML report ─────────────────────────────────────────────────────────────── + +type matrixCertData struct { + SubjectCommonName string + IssuerCommonName string + SHA256Fingerprint string + DNSNames []string +} + +type matrixConnectionData struct { + Address string + TLSVersion string + CipherSuite string + Certs []matrixCertData + AllChecksOK bool + CheckDetails []matrixCheckItem + Errors []string + Open bool +} + +type matrixCheckItem struct { + Label string + OK bool +} + +type matrixConnErrData struct { + Address string + Message string +} + +type matrixSRVRecord struct { + Target string + Port uint16 + Priority uint16 + Weight uint16 +} + +type matrixHostData struct { + Name string + CName string + Addrs []string +} + +type matrixTemplateData struct { + FederationOK bool + Version string + VersionError string + WellKnownServer string + WellKnownResult string + SRVSkipped bool + SRVCName string + SRVRecords []matrixSRVRecord + SRVError string + Hosts []matrixHostData + Addrs []string + Connections []matrixConnectionData + ConnectionErrors []matrixConnErrData +} + +var matrixHTMLTemplate = template.Must( + template.New("matrix").Parse(` + + + + +Matrix Federation Report + + + + +
+

Matrix Federation

+ {{if .FederationOK}} + Federation OK + {{- else}} + Federation FAIL + {{- end}} + {{if .Version}}
Server: {{.Version}}{{if .VersionError}} — {{.VersionError}}{{end}}
{{end}} +
+ +{{if .Connections}} +
+

Connections ({{len .Connections}})

+ {{range .Connections}} + + + {{.Address}} + {{if .AllChecksOK}}All checks OK{{else}}Checks failed{{end}} + +
+ {{if or .TLSVersion .CipherSuite}} +

TLS

+

{{.TLSVersion}}{{if and .TLSVersion .CipherSuite}} — {{end}}{{.CipherSuite}}

+ {{end}} + + {{if .Certs}} +

Certificates

+ + + {{range .Certs}} + + + + + + + {{end}} +
SubjectIssuerDNS NamesFingerprint (SHA-256)
{{.SubjectCommonName}}{{.IssuerCommonName}}{{range .DNSNames}}{{.}} {{end}}{{.SHA256Fingerprint}}
+ {{end}} + + {{if .CheckDetails}} +

Checks

+ + {{range .CheckDetails}} + + + + + {{end}} +
{{if .OK}}{{else}}{{end}}{{.Label}}
+ {{end}} + + {{range .Errors}}

⚠ {{.}}

{{end}} +
+ + {{end}} +
+{{end}} + +{{if .ConnectionErrors}} +
+

Connection Errors ({{len .ConnectionErrors}})

+ {{range .ConnectionErrors}} +

{{.Address}}
{{.Message}}

+ {{end}} +
+{{end}} + +
+

Well-Known

+ {{if .WellKnownServer}} +

Server: {{.WellKnownServer}}

+ {{else if .WellKnownResult}} +

{{.WellKnownResult}}

+ {{else}} +

Not found.

+ {{end}} +
+ +
+

DNS Resolution

+ {{if .SRVSkipped}} +

SRV lookup skipped{{if .SRVCName}} (CNAME: {{.SRVCName}}){{end}}

+ {{else if .SRVError}} +

SRV error: {{.SRVError}}

+ {{else if .SRVRecords}} +

SRV Records

+ + + {{range .SRVRecords}} + + + + + + + {{end}} +
TargetPortPriorityWeight
{{.Target}}{{.Port}}{{.Priority}}{{.Weight}}
+ {{else}} +

No SRV records found.

+ {{end}} + + {{if .Hosts}} +

Resolved Hosts

+ {{range .Hosts}} +

+ {{.Name}} + {{if .CName}} → {{.CName}}{{end}} + {{if .Addrs}}: {{range .Addrs}}{{.}} {{end}}{{end}} +

+ {{end}} + {{else if .Addrs}} +

Addresses

+ + {{end}} +
+ + +`), +) + +// GetHTMLReport implements sdk.CheckerHTMLReporter. +func (p *matrixProvider) GetHTMLReport(raw json.RawMessage) (string, error) { + var r MatrixFederationData + if err := json.Unmarshal(raw, &r); err != nil { + return "", fmt.Errorf("failed to unmarshal matrix report: %w", err) + } + + data := matrixTemplateData{ + FederationOK: r.FederationOK, + WellKnownServer: r.WellKnownResult.Server, + WellKnownResult: r.WellKnownResult.Result, + SRVSkipped: r.DNSResult.SRVSkipped, + SRVCName: r.DNSResult.SRVCName, + Addrs: r.DNSResult.Addrs, + } + + // Version + if r.Version.Name != "" || r.Version.Version != "" { + data.Version = strings.TrimSpace(r.Version.Name + " " + r.Version.Version) + } + data.VersionError = r.Version.Error + + // SRV records + for _, s := range r.DNSResult.SRVRecords { + data.SRVRecords = append(data.SRVRecords, matrixSRVRecord{ + Target: s.Target, + Port: s.Port, + Priority: s.Priority, + Weight: s.Weight, + }) + } + + // SRV error + if r.DNSResult.SRVError != nil { + data.SRVError = r.DNSResult.SRVError.Message + } + + // Hosts + for name, h := range r.DNSResult.Hosts { + data.Hosts = append(data.Hosts, matrixHostData{ + Name: name, + CName: h.CName, + Addrs: h.Addrs, + }) + } + + // Successful connections + for addr, cr := range r.ConnectionReports { + conn := matrixConnectionData{ + Address: addr, + TLSVersion: cr.Cipher.Version, + CipherSuite: cr.Cipher.CipherSuite, + AllChecksOK: cr.Checks.AllChecksOK, + Errors: cr.Errors, + Open: !cr.Checks.AllChecksOK, + } + for _, cert := range cr.Certificates { + conn.Certs = append(conn.Certs, matrixCertData{ + SubjectCommonName: cert.SubjectCommonName, + IssuerCommonName: cert.IssuerCommonName, + SHA256Fingerprint: cert.SHA256Fingerprint, + DNSNames: cert.DNSNames, + }) + } + conn.CheckDetails = []matrixCheckItem{ + {"Matching server name", cr.Checks.MatchingServerName}, + {"Certificate valid until future", cr.Checks.FutureValidUntilTS}, + {"Valid certificates", cr.Checks.ValidCertificates}, + {"Has Ed25519 key", cr.Checks.HasEd25519Key}, + {"All Ed25519 checks OK", cr.Checks.AllEd25519ChecksOK}, + } + data.Connections = append(data.Connections, conn) + } + + // Failed connections + for addr, ce := range r.ConnectionErrors { + data.ConnectionErrors = append(data.ConnectionErrors, matrixConnErrData{ + Address: addr, + Message: ce.Message, + }) + } + + var buf strings.Builder + if err := matrixHTMLTemplate.Execute(&buf, data); err != nil { + return "", fmt.Errorf("failed to render matrix HTML report: %w", err) + } + return buf.String(), nil +} diff --git a/checker/rule.go b/checker/rule.go new file mode 100644 index 0000000..5132325 --- /dev/null +++ b/checker/rule.go @@ -0,0 +1,81 @@ +package checker + +import ( + "context" + "fmt" + "strings" + + sdk "git.happydns.org/checker-sdk-go/checker" +) + +// Rule returns a new matrix federation check rule. +func Rule() sdk.CheckRule { + return &matrixRule{} +} + +type matrixRule struct{} + +func (r *matrixRule) Name() string { + return "matrix_federation" +} + +func (r *matrixRule) Description() string { + return "Checks whether Matrix federation is working correctly" +} + +func (r *matrixRule) ValidateOptions(opts sdk.CheckerOptions) error { + return nil +} + +func (r *matrixRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) sdk.CheckState { + var data MatrixFederationData + if err := obs.Get(ctx, ObservationKeyMatrix, &data); err != nil { + return sdk.CheckState{ + Status: sdk.StatusError, + Message: fmt.Sprintf("Failed to get Matrix federation data: %v", err), + Code: "matrix_federation_error", + } + } + + domain, _ := opts["serviceDomain"].(string) + domain = strings.TrimSuffix(domain, ".") + + if data.FederationOK { + version := strings.TrimSpace(data.Version.Name + " " + data.Version.Version) + return sdk.CheckState{ + Status: sdk.StatusOK, + Message: fmt.Sprintf("Running %s", version), + Code: "matrix_federation_ok", + Meta: map[string]any{ + "version": version, + }, + } + } + + var statusLine string + + if data.DNSResult.SRVError != nil && data.WellKnownResult.Result != "" { + statusLine = fmt.Sprintf("%s OR %s", data.DNSResult.SRVError.Message, data.WellKnownResult.Result) + } else if len(data.ConnectionErrors) > 0 { + var msg strings.Builder + for srv, cerr := range data.ConnectionErrors { + if msg.Len() > 0 { + msg.WriteString("; ") + } + msg.WriteString(srv) + msg.WriteString(": ") + msg.WriteString(cerr.Message) + } + statusLine = fmt.Sprintf("Connection errors: %s", msg.String()) + } else if data.WellKnownResult.Server != domain { + statusLine = fmt.Sprintf("Bad homeserver_name: got %s, expected %s", data.WellKnownResult.Server, domain) + } else { + statusLine = fmt.Sprintf("Federation broken. Check https://federationtester.matrix.org/#%s", domain) + } + + return sdk.CheckState{ + Status: sdk.StatusCrit, + Message: statusLine, + Code: "matrix_federation_fail", + } +} diff --git a/checker/types.go b/checker/types.go new file mode 100644 index 0000000..394e48f --- /dev/null +++ b/checker/types.go @@ -0,0 +1,72 @@ +// Package checker implements the Matrix federation checker for happyDomain. +// +// It queries a Matrix Federation Tester instance (by default the public one +// hosted at https://federationtester.matrix.org) to verify that a Matrix +// homeserver is correctly federating, then exposes the result both as an +// observation and as a rich HTML report. +package checker + +import ( + sdk "git.happydns.org/checker-sdk-go/checker" +) + +// ObservationKeyMatrix is the observation key for Matrix federation test data. +const ObservationKeyMatrix sdk.ObservationKey = "matrix_federation" + +// MatrixFederationData is the full payload returned by the Matrix Federation +// Tester API and stored as the observation. +type MatrixFederationData struct { + WellKnownResult struct { + Server string `json:"m.server"` + Result string `json:"result"` + CacheExpiresAt int64 `json:"CacheExpiresAt"` + } `json:"WellKnownResult"` + DNSResult struct { + SRVSkipped bool `json:"SRVSkipped"` + SRVCName string `json:"SRVCName"` + SRVRecords []struct { + Target string `json:"Target"` + Port uint16 `json:"Port"` + Priority uint16 `json:"Priority"` + Weight uint16 `json:"Weight"` + } `json:"SRVRecords"` + SRVError *struct { + Message string `json:"Message"` + } `json:"SRVError"` + Hosts map[string]struct { + CName string `json:"CName"` + Addrs []string `json:"Addrs"` + } `json:"Hosts"` + Addrs []string `json:"Addrs"` + } `json:"DNSResult"` + ConnectionReports map[string]struct { + Certificates []struct { + SubjectCommonName string `json:"SubjectCommonName"` + IssuerCommonName string `json:"IssuerCommonName"` + SHA256Fingerprint string `json:"SHA256Fingerprint"` + DNSNames []string `json:"DNSNames"` + } `json:"Certificates"` + Cipher struct { + Version string `json:"Version"` + CipherSuite string `json:"CipherSuite"` + } `json:"Cipher"` + Checks struct { + AllChecksOK bool `json:"AllChecksOK"` + MatchingServerName bool `json:"MatchingServerName"` + FutureValidUntilTS bool `json:"FutureValidUntilTS"` + HasEd25519Key bool `json:"HasEd25519Key"` + AllEd25519ChecksOK bool `json:"AllEd25519ChecksOK"` + ValidCertificates bool `json:"ValidCertificates"` + } `json:"Checks"` + Errors []string `json:"Errors"` + } `json:"ConnectionReports"` + ConnectionErrors map[string]struct { + Message string `json:"Message"` + } `json:"ConnectionErrors"` + Version struct { + Name string `json:"name"` + Version string `json:"version"` + Error string `json:"error,omitempty"` + } `json:"Version"` + FederationOK bool `json:"FederationOK"` +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..28304e8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.happydns.org/checker-matrix + +go 1.25.0 + +require git.happydns.org/checker-sdk-go v0.0.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5282be1 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.happydns.org/checker-sdk-go v0.0.1 h1:4RxCJr73HWKxjOyU/6NJMO8lXJmH0gMLA68EzTqLbQI= +git.happydns.org/checker-sdk-go v0.0.1/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2d19c41 --- /dev/null +++ b/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "flag" + "log" + + matrix "git.happydns.org/checker-matrix/checker" + sdk "git.happydns.org/checker-sdk-go/checker" +) + +// Version is the standalone binary's version. It defaults to "custom-build" +// and is meant to be overridden by the CI at link time: +// +// go build -ldflags "-X main.Version=1.2.3" . +var Version = "custom-build" + +var listenAddr = flag.String("listen", ":8080", "HTTP listen address") + +func main() { + flag.Parse() + + // Propagate the binary version to the checker package so it shows up in + // CheckerDefinition.Version. + matrix.Version = Version + + server := sdk.NewServer(matrix.Provider()) + if err := server.ListenAndServe(*listenAddr); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000..3ce631f --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,24 @@ +// Command plugin is the happyDomain plugin entrypoint for the matrix checker. +// +// It is built as a Go plugin (`go build -buildmode=plugin`) and loaded at +// runtime by happyDomain. +package main + +import ( + matrix "git.happydns.org/checker-matrix/checker" + sdk "git.happydns.org/checker-sdk-go/checker" +) + +// Version is the plugin's version. It defaults to "custom-build" and is +// meant to be overridden by the CI at link time: +// +// go build -buildmode=plugin -ldflags "-X main.Version=1.2.3" -o checker-matrix.so ./plugin +var Version = "custom-build" + +// NewCheckerPlugin is the symbol resolved by happyDomain when loading the +// .so file. It returns the checker definition and the observation provider +// that the host will register in its global registries. +func NewCheckerPlugin() (*sdk.CheckerDefinition, sdk.ObservationProvider, error) { + matrix.Version = Version + return matrix.Definition(), matrix.Provider(), nil +}