diff --git a/.gitignore b/.gitignore
index 5b90e79..444ce48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ go.work.sum
# env file
.env
+esctl
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..719e4ae
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,87 @@
+# Copyright © 2026 Thomas von Dein
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+#
+# no need to modify anything below
+tool = esctl
+VERSION = $(shell grep VERSION config.go | head -1 | cut -d '"' -f2)
+archs = darwin freebsd linux windows
+PREFIX = /usr/local
+UID = root
+GID = 0
+HAVE_POD := $(shell pod2text -h 2>/dev/null)
+
+all: buildlocal
+
+
+buildlocal:
+ CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o $(tool)
+ strip $(tool)
+
+install: buildlocal
+ install -d -o $(UID) -g $(GID) $(PREFIX)/bin
+ install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
+ install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
+ install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
+
+clean:
+ rm -rf $(tool) coverage.out testdata t/out
+
+test: clean
+ mkdir -p t/out
+ go test ./... $(ARGS)
+
+testlint: test lint
+
+lint:
+ golangci-lint run
+
+lint-full:
+ golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest
+
+testfuzzy: clean
+ go test -fuzz ./... $(ARGS)
+
+singletest:
+ @echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v"
+ go test -run $(TEST) $(ARGS)
+
+cover-report:
+ go test ./... -cover -coverprofile=coverage.out
+ go tool cover -html=coverage.out
+
+goupdate:
+ go get -t -u=patch ./...
+
+buildall:
+ ./mkrel.sh $(tool) $(VERSION)
+
+release:
+ gh release create v$(VERSION) --generate-notes
+
+show-versions: buildlocal
+ @echo "### esctl version:"
+ @./gfn -V
+
+ @echo
+ @echo "### go module versions:"
+ @go list -m all
+
+ @echo
+ @echo "### go version used for building:"
+ @grep -m 1 go go.mod
+
+# lint:
+# golangci-lint run -p bugs -p unused
diff --git a/README.md b/README.md
index 04e234b..9189e98 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,99 @@
# esctl
-Elasticsearch CLI
\ No newline at end of file
+Elasticsearch CLI
+
+## Usage
+
+```console
+NAME:
+ esctl - manage elasticsearch from cli
+
+USAGE:
+ esctl [global options] [command [command options]]
+
+VERSION:
+ v0.0.1
+
+COMMANDS:
+ health show ES health
+ search, / search within an index
+ help, h Shows a list of commands or help for one command
+
+GLOBAL OPTIONS:
+ --debug, -d enable debugging [$ES_DEBUG]
+ --help, -h show help
+ --version, -v print the version
+```
+
+Configure `esctl` with environment variables:
+
+- `ES_URI`: elasticsearch uri
+- `ES_USER`: username
+- `ES_PASS`: password
+
+## Introduction
+
+FIXME
+
+## Installation
+
+The tool does not have any dependencies. Just download the binary for
+your platform from the releases page and you're good to go.
+
+### Installation using a pre-compiled binary
+
+You can use [stew](https://github.com/marwanhawari/stew) to install esctl:
+```default
+stew install https://codeberg.org/scip/esctl
+```
+
+Or go to the [latest release page](https://codeberg.org/scip/esctl/releases/)
+and look for your OS and platform. There are two options to install the binary:
+
+Directly download the binary for your platform,
+e.g. `esctl-linux-amd64-0.0.2`, rename it to `esctl` (or whatever
+you like more!) and put it into your bin dir (e.g. `$HOME/bin` or as
+root to `/usr/local/bin`).
+
+Be sure to verify the signature of the binary file. For this also
+download the matching `esctl-linux-amd64-0.0.2.sha256` file and:
+
+```shell
+cat esctl-linux-amd64-0.0.2.sha25 && sha256sum esctl-linux-amd64-0.0.2
+```
+You should see the same SHA256 hash.
+
+You may also download a binary tarball for your platform, e.g.
+`esctl-linux-amd64-0.0.2.tar.gz`, unpack and install it. GNU Make is
+required for this:
+
+```shell
+tar xvfz esctl-linux-amd64-0.0.2.tar.gz
+cd esctl-linux-amd64-0.0.2
+sudo make install
+```
+
+### Installation from source
+
+Check out the repository and execute `go build`, then copy the
+compiled binary to your `$PATH`.
+
+Or, if you have GNU Make installed, just execute:
+
+```default
+make
+sudo make install
+```
+
+# Report bugs
+
+[Please open an issue](https://codeberg.org/scip/esctl/issues). Thanks!
+
+# License
+
+This work is licensed under the terms of the General Public Licens
+version 3.
+
+# Author
+
+Copyleft (c) 2026 Thomas von Dein
diff --git a/cmd/health.go b/cmd/health.go
new file mode 100644
index 0000000..a82dc7d
--- /dev/null
+++ b/cmd/health.go
@@ -0,0 +1,41 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package cmd
+
+import (
+ "context"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+ "codeberg.org/scip/esctl/pkg/es"
+
+ "github.com/urfave/cli/v3"
+)
+
+func Health(conf *cfg.Config) *cli.Command {
+ return &cli.Command{
+ Name: "health",
+ Usage: "show ES health",
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ if err := es.Health(conf); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ }
+}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 0000000..f66f6c9
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,74 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+ "codeberg.org/scip/esctl/pkg/log"
+
+ "github.com/urfave/cli/v3"
+)
+
+func Finish(err error) int {
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Error: ", err.Error())
+ return 1
+ }
+
+ return 0
+}
+
+func Main() int {
+ conf, err := cfg.Init()
+ if err != nil {
+ return Finish(err)
+ }
+
+ cmd := &cli.Command{
+ Name: "esctl",
+ Usage: "manage elasticsearch from cli",
+ Version: cfg.Version,
+ EnableShellCompletion: true,
+
+ Flags: []cli.Flag{
+ &cli.BoolFlag{
+ Name: "debug",
+ Aliases: []string{"d"},
+ Value: false,
+ Usage: "enable debugging",
+ Sources: cli.EnvVars("ES_DEBUG"),
+ Destination: &conf.Debug,
+ },
+ },
+
+ Commands: []*cli.Command{
+ Health(conf),
+ Search(conf),
+ },
+
+ Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
+ log.Init(conf)
+ return nil, nil
+ },
+ }
+
+ return Finish(cmd.Run(context.Background(), os.Args))
+}
diff --git a/cmd/search.go b/cmd/search.go
new file mode 100644
index 0000000..0bfc3d2
--- /dev/null
+++ b/cmd/search.go
@@ -0,0 +1,72 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package cmd
+
+import (
+ "context"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+ "codeberg.org/scip/esctl/pkg/es"
+
+ "github.com/urfave/cli/v3"
+)
+
+func Search(conf *cfg.Config) *cli.Command {
+ return &cli.Command{
+ Name: "search",
+ Aliases: []string{"/"},
+ Usage: "search within an index",
+ UsageText: "search [options] ",
+
+ Flags: []cli.Flag{
+ &cli.StringFlag{
+ Name: "index",
+ Usage: "index to search within",
+ Sources: cli.EnvVars("ES_INDEX"),
+ Destination: &conf.Index,
+ },
+ &cli.IntFlag{
+ Name: "from",
+ Usage: "show results FROM (default 0)",
+ Destination: &conf.From,
+ Value: 0,
+ Aliases: []string{"f"},
+ },
+ &cli.IntFlag{
+ Name: "to",
+ Usage: "show results to (default 10)",
+ Destination: &conf.To,
+ Value: 10,
+ Aliases: []string{"t"},
+ },
+ &cli.StringSliceFlag{
+ Name: "filter",
+ Usage: "additional filters. format: key=value",
+ Destination: &conf.Filter,
+ Aliases: []string{"F"},
+ },
+ },
+
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ if err := es.Search(conf, cmd.Args().Get(0)); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..d1f0f75
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,37 @@
+// Copyright © 2026 Thomas von Dein
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+module codeberg.org/scip/esctl
+
+go 1.24
+
+require (
+ github.com/alecthomas/repr v0.5.2 // indirect
+ github.com/elastic/elastic-transport-go/v8 v8.11.0 // indirect
+ github.com/elastic/go-elasticsearch/v9 v9.3.2 // indirect
+ github.com/fatih/color v1.16.0 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/lmittmann/tint v1.1.3 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.21 // indirect
+ github.com/tlinden/yadu v0.1.3 // indirect
+ github.com/urfave/cli/v3 v3.8.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/otel v1.35.0 // indirect
+ go.opentelemetry.io/otel/metric v1.35.0 // indirect
+ go.opentelemetry.io/otel/trace v1.35.0 // indirect
+ golang.org/x/sys v0.28.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..2306ef2
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,40 @@
+github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
+github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
+github.com/elastic/elastic-transport-go/v8 v8.9.0 h1:KeT/2P54F0xS0S8Y3Pf+tFDg4HmBgReQMB+BMz8dDAs=
+github.com/elastic/elastic-transport-go/v8 v8.9.0/go.mod h1:ssMTvNS2hwf7CaiGsRRsx4gQHFZ/jS/DkLcISxekWzc=
+github.com/elastic/elastic-transport-go/v8 v8.11.0 h1:taYmqC2M6+fZt/+W+ENYh/W5L9+KrlJGOSbEJs8egWc=
+github.com/elastic/elastic-transport-go/v8 v8.11.0/go.mod h1:DZQ0szCNywc9F+C9l/Kkd4n69SvJVj0I3yK1Of7s3l8=
+github.com/elastic/go-elasticsearch/v9 v9.3.2 h1:nvtvfN/Gsp/rzUPz/9yILwDAsYJ3s5L0VmhR16zPKCA=
+github.com/elastic/go-elasticsearch/v9 v9.3.2/go.mod h1:ubKUMJCJbX5V/gW5MIn2NQZyaEZ61ubXwJmD5UMNrM8=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
+github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
+github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
+github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY=
+github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA=
+github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
+github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..222b8a9
--- /dev/null
+++ b/main.go
@@ -0,0 +1,27 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package main
+
+import (
+ "os"
+
+ "codeberg.org/scip/esctl/cmd"
+)
+
+func main() {
+ os.Exit(cmd.Main())
+}
diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go
new file mode 100644
index 0000000..e21a6ba
--- /dev/null
+++ b/pkg/cfg/config.go
@@ -0,0 +1,73 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package cfg
+
+import (
+ "crypto/tls"
+ "errors"
+ "net/http"
+ "os"
+
+ "github.com/elastic/elastic-transport-go/v8/elastictransport"
+ "github.com/elastic/go-elasticsearch/v9"
+)
+
+const (
+ Version string = `v0.0.1`
+)
+
+type Config struct {
+ Uri, User, Pass string
+ ES *elasticsearch.TypedClient
+ Debug bool
+ From, To int
+ Index string
+ Filter []string
+}
+
+func Init() (*Config, error) {
+ cfg := Config{
+ Uri: os.Getenv("ES_URI"),
+ User: os.Getenv("ES_USER"),
+ Pass: os.Getenv("ES_PASS"),
+ }
+
+ switch {
+ case cfg.Uri == "":
+ return nil, errors.New("ES_URI unset")
+ case cfg.User == "":
+ return nil, errors.New("ES_USER unset")
+ case cfg.Pass == "":
+ return nil, errors.New("ES_PASS unset")
+ }
+
+ es, _ := elasticsearch.NewTyped(
+ elasticsearch.WithAddresses(cfg.Uri),
+ elasticsearch.WithBasicAuth(cfg.User, cfg.Pass),
+ elasticsearch.WithTransportOptions(
+ elastictransport.WithTransport(
+ &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ },
+ ),
+ ),
+ )
+
+ cfg.ES = es
+
+ return &cfg, nil
+}
diff --git a/pkg/es/aux.go b/pkg/es/aux.go
new file mode 100644
index 0000000..05fe721
--- /dev/null
+++ b/pkg/es/aux.go
@@ -0,0 +1,36 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package es
+
+import (
+ "context"
+ "log"
+ "log/slog"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+)
+
+func Health(conf *cfg.Config) error {
+ res, err := conf.ES.Cluster.Health().Do(context.Background())
+ if err != nil {
+ log.Fatalf("Error getting health: %s", err)
+ }
+
+ slog.Info("ES result", "cluster health", res)
+
+ return nil
+}
diff --git a/pkg/es/search.go b/pkg/es/search.go
new file mode 100644
index 0000000..fe7b76b
--- /dev/null
+++ b/pkg/es/search.go
@@ -0,0 +1,72 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package es
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+ "github.com/elastic/go-elasticsearch/v9/typedapi/core/search"
+ "github.com/elastic/go-elasticsearch/v9/typedapi/esdsl"
+ "github.com/elastic/go-elasticsearch/v9/typedapi/types"
+)
+
+/*
+Execute an ES search.
+
+ q is the actual search query given as arg to the 'search' cmd
+ additional filters can be given as -F key=value
+*/
+func Search(conf *cfg.Config, q string) error {
+ query := esdsl.NewBoolQuery().
+ Must(esdsl.NewMatchQuery("message", q))
+
+ filters := make([]types.QueryVariant, len(conf.Filter))
+
+ for idx, filter := range conf.Filter {
+ parts := strings.Split(filter, "=")
+ if len(parts) != 2 {
+ return fmt.Errorf("invalid filter spec: %s, expecting key=value", filter)
+ }
+
+ filters[idx] = esdsl.NewTermQuery(parts[0], esdsl.NewFieldValue().String(parts[1]))
+ }
+
+ if len(filters) > 0 {
+ query.Filter(filters...)
+ }
+
+ res, err := conf.ES.Search().
+ Index(conf.Index).
+ Request(&search.Request{
+ Query: query.QueryCaster(),
+ From: &conf.From,
+ Size: &conf.To,
+ }).
+ Do(context.Background())
+ if err != nil {
+ return fmt.Errorf("Error running search (esdsl): %s", err)
+ }
+
+ for _, hit := range res.Hits.Hits {
+ fmt.Printf("%s\n", hit.Source_)
+ }
+
+ return nil
+}
diff --git a/pkg/log/logger.go b/pkg/log/logger.go
new file mode 100644
index 0000000..c65791f
--- /dev/null
+++ b/pkg/log/logger.go
@@ -0,0 +1,59 @@
+/*
+Copyright © 2026 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package log
+
+import (
+ "log/slog"
+ "os"
+ "runtime/debug"
+
+ "codeberg.org/scip/esctl/pkg/cfg"
+ "github.com/mattn/go-isatty"
+ "github.com/tlinden/yadu"
+)
+
+const LevelNotice = slog.Level(2)
+
+func Init(conf *cfg.Config) {
+ logLevel := &slog.LevelVar{}
+
+ opts := &yadu.Options{
+ Level: logLevel,
+ AddSource: true,
+ NoColor: !isatty.IsTerminal(os.Stdout.Fd()),
+ }
+
+ buildInfo, _ := debug.ReadBuildInfo()
+
+ handler := yadu.NewHandler(os.Stdout, opts)
+ debuglogger := slog.New(handler).With(
+ slog.Group("program_info",
+ slog.String("version", cfg.Version),
+ slog.String("go_version", buildInfo.GoVersion),
+ ),
+ )
+
+ slog.SetDefault(debuglogger)
+
+ switch conf.Debug {
+ case true:
+ logLevel.Set(slog.LevelDebug)
+
+ default:
+ logLevel.Set(slog.LevelInfo)
+ }
+}