Compare commits
31 Commits
eebb557fce
...
v0.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| fdb0f0324f | |||
| 9aef92f32d | |||
| e5baf53758 | |||
| 0fe451eed0 | |||
| cb5ea80e66 | |||
| f7da9519fa | |||
| 589fb2f0e1 | |||
| f5ee63e5aa | |||
| 0de2e3f4d9 | |||
| c92873f07d | |||
| 4d25ea1d16 | |||
| 0d14510958 | |||
| c3b47ba812 | |||
| 15f5aab449 | |||
| 7cf3ee04f5 | |||
| a3e3cde4c4 | |||
| a4a10d0226 | |||
| 06e755ecd2 | |||
| b15f394199 | |||
| f225e15b2c | |||
| 5d946e4e93 | |||
| 979324f151 | |||
| be9da490ff | |||
| 3599812072 | |||
| 7e6c53c5f5 | |||
| 5f924dcb00 | |||
| ee63e80f77 | |||
| 1b9ce9f688 | |||
| 1ea1ab4ac4 | |||
| a3f8e2fee3 | |||
| f714210b37 |
@@ -0,0 +1 @@
|
|||||||
|
.tmp
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
.PHONY: all clean install generate fmt tidy
|
||||||
|
.PHONY: FORCE
|
||||||
|
|
||||||
|
GO ?= go
|
||||||
|
GOFMT ?= gofmt
|
||||||
|
GOFMT_FLAGS = -w -l -s
|
||||||
|
GOGENERATE_FLAGS = -v
|
||||||
|
|
||||||
|
GOPATH ?= $(shell $(GO) env GOPATH)
|
||||||
|
GOBIN ?= $(GOPATH)/bin
|
||||||
|
|
||||||
|
TOOLSDIR := $(CURDIR)/pkg/tools
|
||||||
|
TMPDIR ?= .tmp
|
||||||
|
|
||||||
|
REVIVE ?= $(GOBIN)/revive
|
||||||
|
REVIVE_CONF ?= $(TOOLSDIR)/revive.toml
|
||||||
|
REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly
|
||||||
|
REVIVE_INSTALL_URL ?= github.com/mgechev/revive
|
||||||
|
|
||||||
|
GO_INSTALL_URLS = \
|
||||||
|
$(REVIVE_INSTALL_URL) \
|
||||||
|
|
||||||
|
V = 0
|
||||||
|
Q = $(if $(filter 1,$V),,@)
|
||||||
|
M = $(shell if [ "$$(tput colors 2> /dev/null || echo 0)" -ge 8 ]; then printf "\033[34;1m▶\033[0m"; else printf "▶"; fi)
|
||||||
|
|
||||||
|
GO_BUILD = $(GO) build -v
|
||||||
|
GO_BUILD_CMD = $(GO_BUILD) -o "$(OUTDIR)"
|
||||||
|
|
||||||
|
all: get generate tidy build
|
||||||
|
|
||||||
|
install:
|
||||||
|
$Q $(GO) install -v ./cmd/...
|
||||||
|
|
||||||
|
clean: ; $(info $(M) cleaning…)
|
||||||
|
rm -rf $(TMPDIR)
|
||||||
|
|
||||||
|
$(TMPDIR)/index: $(TOOLSDIR)/gen_index.sh Makefile FORCE ; $(info $(M) generating index…)
|
||||||
|
$Q mkdir -p $(@D)
|
||||||
|
$Q $< > $@~
|
||||||
|
$Q if cmp $@ $@~ 2> /dev/null >&2; then rm $@~; else mv $@~ $@; fi
|
||||||
|
|
||||||
|
$(TMPDIR)/gen.mk: $(TOOLSDIR)/gen_mk.sh $(TMPDIR)/index Makefile ; $(info $(M) generating subproject rules…)
|
||||||
|
$Q mkdir -p $(@D)
|
||||||
|
$Q $< $(TMPDIR)/index > $@~
|
||||||
|
$Q if cmp $@ $@~ 2> /dev/null >&2; then rm $@~; else mv $@~ $@; fi
|
||||||
|
|
||||||
|
include $(TMPDIR)/gen.mk
|
||||||
|
|
||||||
|
fmt: ; $(info $(M) reformatting sources…)
|
||||||
|
$Q find . -name '*.go' | xargs -r $(GOFMT) $(GOFMT_FLAGS)
|
||||||
|
|
||||||
|
tidy: fmt
|
||||||
|
|
||||||
|
generate: ; $(info $(M) running go:generate…)
|
||||||
|
$Q git grep -l '^//go:generate' | sort -uV | xargs -r -n1 $(GO) generate $(GOGENERATE_FLAGS)
|
||||||
|
|
||||||
|
$(REVIVE):
|
||||||
|
$Q $(GO) install -v $(REVIVE_INSTALL_URL)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Config describes the repository
|
||||||
|
type Config struct {
|
||||||
|
Base string
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg = &Config{
|
||||||
|
Base: "./m",
|
||||||
|
Domain: "m.jpi.cloud",
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/burntSushi/toml"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"git.jpi.io/amery/jpictl/pkg/zones"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command
|
||||||
|
var dumpCmd = &cobra.Command{
|
||||||
|
Use: "dump",
|
||||||
|
Short: "generates a toml representation of the config",
|
||||||
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
m, err := zones.New(cfg.Base, cfg.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := toml.NewEncoder(&buf)
|
||||||
|
if err = enc.Encode(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = buf.WriteTo(os.Stdout); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(dumpCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"darvaza.org/sidecar/pkg/logger/zerolog"
|
||||||
|
"darvaza.org/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
log = zerolog.New(nil, slog.Debug)
|
||||||
|
)
|
||||||
|
|
||||||
|
// fatal is a convenience wrapper for slog.Logger.Fatal().Print()
|
||||||
|
func fatal(err error, msg string, args ...any) {
|
||||||
|
l := log.Fatal()
|
||||||
|
if err != nil {
|
||||||
|
l = l.WithField(slog.ErrorFieldName, err)
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
msg = fmt.Sprintf(msg, args...)
|
||||||
|
}
|
||||||
|
l.Print(msg)
|
||||||
|
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Package main implements the jpictl command
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CmdName is the name of the executable
|
||||||
|
CmdName = "jpictl"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: CmdName,
|
||||||
|
Short: "control tool for jpi.cloud",
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fatal(err, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,41 @@
|
|||||||
module git.jpi.io/amery/jpictl
|
module git.jpi.io/amery/jpictl
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
darvaza.org/core v0.9.5
|
||||||
|
darvaza.org/resolver v0.5.2
|
||||||
|
darvaza.org/sidecar v0.0.0-20230721122716-b9c54b8adbaf
|
||||||
|
darvaza.org/slog v0.5.2
|
||||||
|
github.com/burntSushi/toml v0.3.1
|
||||||
|
github.com/mgechev/revive v1.3.2
|
||||||
|
github.com/spf13/cobra v1.7.0
|
||||||
|
gopkg.in/gcfg.v1 v1.2.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
darvaza.org/slog/handlers/filter v0.4.4 // indirect
|
||||||
|
darvaza.org/slog/handlers/zerolog v0.4.4 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
|
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
|
||||||
|
github.com/fatih/color v1.15.0 // indirect
|
||||||
|
github.com/fatih/structtag v1.2.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
|
||||||
|
github.com/miekg/dns v1.1.55 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
|
github.com/rs/zerolog v1.30.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
|
golang.org/x/net v0.14.0 // indirect
|
||||||
|
golang.org/x/sys v0.11.0 // indirect
|
||||||
|
golang.org/x/text v0.12.0 // indirect
|
||||||
|
golang.org/x/tools v0.12.0 // indirect
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
darvaza.org/core v0.9.5 h1:sS5pZFwicaxJIQixEiqkMr9GknVHYL+EbKDMkR/4jDM=
|
||||||
|
darvaza.org/core v0.9.5/go.mod h1:O3tHBMlw+xB47uGh5CUx7dXAujBAMmD8BCRFPZmIw54=
|
||||||
|
darvaza.org/resolver v0.5.2 h1:VjHhEr/MJBszeDb7tYlXQ9Bsyh4xrDR7Sd10WAmPD6k=
|
||||||
|
darvaza.org/resolver v0.5.2/go.mod h1:fFvsVPEFeMzUIWlLG47Go/6uJYtRLb9R8HIgYg3uaxE=
|
||||||
|
darvaza.org/sidecar v0.0.0-20230721122716-b9c54b8adbaf h1:ya5ZQicBb/GWll3rlqra8No7oJXks7y1m/cJGYBypv4=
|
||||||
|
darvaza.org/sidecar v0.0.0-20230721122716-b9c54b8adbaf/go.mod h1:by+bPsMa7Rxc/ZYG1qBunrtKocv/DkrPBmyFlmq/j2Q=
|
||||||
|
darvaza.org/slog v0.5.2 h1:8TG1WyHjOyh2vW6t3pjzZVaWzpko5MIIpeI7LWqHFvs=
|
||||||
|
darvaza.org/slog v0.5.2/go.mod h1:HAkEpxTA/mkiLNUXJo5qsCh8EVCtA3evje8GAaCDWHI=
|
||||||
|
darvaza.org/slog/handlers/filter v0.4.4 h1:b2e2T9fQzMdJ0ia+f6b7kw9/T9GFwhFCKob/2tqhGGU=
|
||||||
|
darvaza.org/slog/handlers/filter v0.4.4/go.mod h1:cQlJWuolB6guLug09sX/8Zrzct++M6SPCGvXR37E7Cc=
|
||||||
|
darvaza.org/slog/handlers/zerolog v0.4.4 h1:OR1ASvH1fBCq3t85t4OU6oJPPuqMB1tsDoSpsh6HVJU=
|
||||||
|
darvaza.org/slog/handlers/zerolog v0.4.4/go.mod h1:t60TeEbFcMLo74CkXC2S0rKlnwF4ixZyBR4fqIJV1GE=
|
||||||
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/burntSushi/toml v0.3.1 h1:Hu1cOEC2qtKULZJCzym5tyA35bZr3HREuolgiAzMlhY=
|
||||||
|
github.com/burntSushi/toml v0.3.1/go.mod h1:sGTquCpRYr9McuHdv0m6YKIhx8DJGJa4t04/Y9pfSio=
|
||||||
|
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0=
|
||||||
|
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
|
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||||
|
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0=
|
||||||
|
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
|
||||||
|
github.com/mgechev/revive v1.3.2 h1:Wb8NQKBaALBJ3xrrj4zpwJwqwNA6nDpyJSEQWcCka6U=
|
||||||
|
github.com/mgechev/revive v1.3.2/go.mod h1:UCLtc7o5vg5aXCwdUTU1kEBQ1v+YXPAkYDIDXbrs5I0=
|
||||||
|
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||||
|
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
||||||
|
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
|
||||||
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||||
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||||
|
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
|
||||||
|
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
Executable
+63
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
: ${GO:=go}
|
||||||
|
|
||||||
|
MODULES=$(find * -name go.mod -exec dirname '{}' \;)
|
||||||
|
GROUPS="pkg cmd"
|
||||||
|
BASE="$PWD"
|
||||||
|
|
||||||
|
mod() {
|
||||||
|
local d="${1:-.}"
|
||||||
|
grep ^module "$d/go.mod" | cut -d' ' -f2
|
||||||
|
}
|
||||||
|
|
||||||
|
namedir() {
|
||||||
|
local d="$1" g= n=
|
||||||
|
|
||||||
|
if [ "." = "$d" ]; then
|
||||||
|
echo "root"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for g in $GROUPS; do
|
||||||
|
n="${d#$g/}"
|
||||||
|
if [ "x$n" != "x$d" ]; then
|
||||||
|
echo "$n" | tr '/' '-'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$d" | tr '/' '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_replace() {
|
||||||
|
local d="$1"
|
||||||
|
grep "=>" "$d/go.mod" | sed -n -e "s;^.*\($ROOT_MODULE.*\)[ \t]\+=>.*;\1;p"
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_index() {
|
||||||
|
local d= n=
|
||||||
|
|
||||||
|
for d; do
|
||||||
|
n=$(namedir "$d")
|
||||||
|
m=$(mod "$d")
|
||||||
|
echo "$n:$d:$m"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_MODULE=$(mod)
|
||||||
|
INDEX=$(gen_index $MODULES)
|
||||||
|
|
||||||
|
echo "$INDEX" | while IFS=: read name dir mod; do
|
||||||
|
deps=
|
||||||
|
for dep in $(mod_replace "$dir"); do
|
||||||
|
depname=$(echo "$INDEX" | grep ":$dep$" | cut -d: -f1 | tr '\n' ',' | sed -e 's|,\+$||g')
|
||||||
|
if [ -n "$depname" ]; then
|
||||||
|
deps="${deps:+$deps,}$depname"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$name:$dir:$mod:$deps"
|
||||||
|
done | sort -V
|
||||||
Executable
+196
@@ -0,0 +1,196 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
INDEX="$1"
|
||||||
|
|
||||||
|
PROJECTS="$(cut -d':' -f1 "$INDEX")"
|
||||||
|
COMMANDS="tidy get build test up"
|
||||||
|
|
||||||
|
expand() {
|
||||||
|
local prefix="$1" suffix="$2"
|
||||||
|
local x= out=
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
for x; do
|
||||||
|
out="${out:+$out }${prefix}$x${suffix}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixed() {
|
||||||
|
local prefix="${1:+$1-}"
|
||||||
|
shift
|
||||||
|
expand "$prefix" "" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
suffixed() {
|
||||||
|
local suffix="${1:+-$1}"
|
||||||
|
shift
|
||||||
|
expand "" "$suffix" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# packed remove excess whitespace from lines of commands
|
||||||
|
packed() {
|
||||||
|
sed -e 's/^[ \t]\+//' -e 's/[ \t]\+$//' -e '/^$/d;' -e '/^#/d';
|
||||||
|
}
|
||||||
|
|
||||||
|
# packet_oneline converts a multiline script into packed single-line equivalent
|
||||||
|
packed_oneline() {
|
||||||
|
packed | tr '\n' ';' | sed -e 's|;$||' -e 's|then;|then |g' -e 's|;[ \t]*|; |g'
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_install_tools() {
|
||||||
|
cat <<EOT
|
||||||
|
for url in \$(GO_INSTALL_URLS); do \$(GO) install -v \$\$url; done
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_revive_exclude() {
|
||||||
|
local self="$1"
|
||||||
|
local dirs= d=
|
||||||
|
|
||||||
|
dirs="$(cut -d: -f2 "$INDEX" | grep -v '^.$' || true)"
|
||||||
|
if [ "." != "$self" ]; then
|
||||||
|
dirs=$(echo "$dirs" | sed -n -e "s;^$self/\(.*\)$;\1;p")
|
||||||
|
fi
|
||||||
|
|
||||||
|
for d in $dirs; do
|
||||||
|
printf -- "-exclude ./$d/... "
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
for cmd in $COMMANDS; do
|
||||||
|
all="$(prefixed $cmd $PROJECTS)"
|
||||||
|
depsx=
|
||||||
|
|
||||||
|
cat <<EOT
|
||||||
|
.PHONY: $cmd $all
|
||||||
|
$cmd: $all
|
||||||
|
|
||||||
|
EOT
|
||||||
|
|
||||||
|
# default calls
|
||||||
|
case "$cmd" in
|
||||||
|
tidy)
|
||||||
|
call="$(cat <<-EOT | packed
|
||||||
|
\$(GO) mod tidy
|
||||||
|
|
||||||
|
# go vet and revive only if there are .go files
|
||||||
|
#
|
||||||
|
$(cat <<-EOL | packed_oneline
|
||||||
|
set -e
|
||||||
|
FILES="\$\$(\$(GO) list -f '{{len .GoFiles}}' ./...)"
|
||||||
|
if [ -n "\$\$FILES" ]; then
|
||||||
|
\$(GO) vet ./...
|
||||||
|
\$(REVIVE) \$(REVIVE_RUN_ARGS) ./...
|
||||||
|
fi
|
||||||
|
EOL
|
||||||
|
)
|
||||||
|
EOT
|
||||||
|
)"
|
||||||
|
depsx="fmt \$(REVIVE)"
|
||||||
|
;;
|
||||||
|
up)
|
||||||
|
call="\$(GO) get -u -v ./...
|
||||||
|
\$(GO) mod tidy"
|
||||||
|
;;
|
||||||
|
test)
|
||||||
|
call="\$(GO) $cmd ./..."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
call="\$(GO) $cmd -v ./..."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
|
build|test)
|
||||||
|
sequential=true ;;
|
||||||
|
*)
|
||||||
|
sequential=false ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while IFS=: read name dir mod deps; do
|
||||||
|
|
||||||
|
deps=$(echo "$deps" | tr ',' ' ')
|
||||||
|
|
||||||
|
# cd $dir
|
||||||
|
if [ "." = "$dir" ]; then
|
||||||
|
# root
|
||||||
|
cd=
|
||||||
|
else
|
||||||
|
cd="cd '$dir'; "
|
||||||
|
fi
|
||||||
|
|
||||||
|
callx="$call"
|
||||||
|
if [ "$name" = root ]; then
|
||||||
|
# special case
|
||||||
|
case "$cmd" in
|
||||||
|
get)
|
||||||
|
cmdx="get -tags tools"
|
||||||
|
;;
|
||||||
|
up)
|
||||||
|
cmdx="get -tags tools -u"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cmdx=
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -z "$cmdx" ] || cmdx="\$(GO) $cmdx -v ./..."
|
||||||
|
|
||||||
|
if [ "up" = "$cmd" ]; then
|
||||||
|
callx="$cmdx
|
||||||
|
\$(GO) mod tidy
|
||||||
|
$(gen_install_tools)"
|
||||||
|
elif [ "get" = "$cmd" ]; then
|
||||||
|
callx="$cmdx
|
||||||
|
$(gen_install_tools)"
|
||||||
|
elif [ -n "$cmdx" ]; then
|
||||||
|
classx="$cmdx"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "build" = "$cmd" ]; then
|
||||||
|
# special build flags for cmd/*
|
||||||
|
#
|
||||||
|
callx="$(cat <<-EOL | packed_oneline
|
||||||
|
set -e
|
||||||
|
MOD="\$\$(\$(GO) list -f '{{.ImportPath}}' ./...)"
|
||||||
|
if echo "\$\$MOD" | grep -q -e '.*/cmd/[^/]\+\$\$'; then
|
||||||
|
\$(GO_BUILD_CMD) ./...
|
||||||
|
elif [ -n "\$\$MOD" ]; then
|
||||||
|
\$(GO_BUILD) ./...
|
||||||
|
fi
|
||||||
|
EOL
|
||||||
|
)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "tidy" = "$cmd" ]; then
|
||||||
|
# exclude submodules when running revive
|
||||||
|
#
|
||||||
|
exclude=$(gen_revive_exclude "$dir")
|
||||||
|
if [ -n "$exclude" ]; then
|
||||||
|
callx=$(echo "$callx" | sed -e "s;\(REVIVE)\);\1 $exclude;")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! $sequential; then
|
||||||
|
deps=
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOT
|
||||||
|
$cmd-$name:${deps:+ $(prefixed $cmd $deps)}${depsx:+ | $depsx} ; \$(info \$(M) $cmd: $name)
|
||||||
|
$(echo "$callx" | sed -e "/^$/d;" -e "s|^|\t\$(Q) $cd|")
|
||||||
|
|
||||||
|
EOT
|
||||||
|
done < "$INDEX"
|
||||||
|
done
|
||||||
|
|
||||||
|
for x in $PROJECTS; do
|
||||||
|
cat <<EOT
|
||||||
|
$x: $(suffixed $x get build tidy)
|
||||||
|
EOT
|
||||||
|
done
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
ignoreGeneratedHeader = false
|
||||||
|
severity = "error"
|
||||||
|
confidence = 0.8
|
||||||
|
errorCode = 1
|
||||||
|
warningCode = 0
|
||||||
|
enableAllRules = true
|
||||||
|
|
||||||
|
[rule.function-length]
|
||||||
|
arguments = [40,0]
|
||||||
|
severity = "warning"
|
||||||
|
[rule.function-result-limit]
|
||||||
|
arguments = [3]
|
||||||
|
[rule.argument-limit]
|
||||||
|
arguments = [5]
|
||||||
|
[rule.cognitive-complexity]
|
||||||
|
arguments = [7]
|
||||||
|
[rule.cyclomatic]
|
||||||
|
arguments = [10]
|
||||||
|
[rule.line-length-limit]
|
||||||
|
arguments = [100]
|
||||||
|
severity = "warning"
|
||||||
|
[rule.comment-spacings]
|
||||||
|
severity = "warning"
|
||||||
|
[rule.empty-lines]
|
||||||
|
severity = "warning"
|
||||||
|
|
||||||
|
# Disabled rules
|
||||||
|
[rule.max-public-structs]
|
||||||
|
disabled = true
|
||||||
|
[rule.file-header]
|
||||||
|
disabled = true
|
||||||
|
[rule.add-constant]
|
||||||
|
disabled = true
|
||||||
|
[rule.banned-characters]
|
||||||
|
disabled = true
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/mgechev/revive"
|
||||||
|
)
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"darvaza.org/core"
|
||||||
|
"gopkg.in/gcfg.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config represents a wgN.conf file
|
||||||
|
type Config struct {
|
||||||
|
Interface InterfaceConfig
|
||||||
|
Peer []PeerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddress is a shortcut to the interface's address
|
||||||
|
func (f *Config) GetAddress() netip.Addr {
|
||||||
|
return f.Interface.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peers tells how many peers are described
|
||||||
|
func (f *Config) Peers() int {
|
||||||
|
return len(f.Peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InterfaceConfig represents the [Interface] section
|
||||||
|
type InterfaceConfig struct {
|
||||||
|
Address netip.Addr
|
||||||
|
PrivateKey BinaryKey
|
||||||
|
ListenPort uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeerConfig represents a [Peer] section
|
||||||
|
type PeerConfig struct {
|
||||||
|
PublicKey BinaryKey
|
||||||
|
Endpoint EndpointAddress
|
||||||
|
AllowedIPs []netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointAddress is a host:port pair to reach the Peer
|
||||||
|
type EndpointAddress struct {
|
||||||
|
Host string
|
||||||
|
Port uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the first part of a hostname
|
||||||
|
func (ep EndpointAddress) Name() string {
|
||||||
|
before, _, _ := strings.Cut(ep.Host, ".")
|
||||||
|
return before
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ep EndpointAddress) String() string {
|
||||||
|
switch {
|
||||||
|
case ep.Host == "":
|
||||||
|
return ""
|
||||||
|
case ep.Port == 0:
|
||||||
|
return ep.Host
|
||||||
|
case !strings.ContainsRune(ep.Host, ':'):
|
||||||
|
return fmt.Sprintf("%s:%v", ep.Host, ep.Port)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("[%s]:%v", ep.Host, ep.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString sets the EndpointAddress from a given "[host]:port"
|
||||||
|
func (ep *EndpointAddress) FromString(s string) error {
|
||||||
|
host, port, err := core.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ep.Host = host
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case port != "":
|
||||||
|
n, _ := strconv.ParseUint(port, 10, 16)
|
||||||
|
ep.Port = uint16(n)
|
||||||
|
default:
|
||||||
|
ep.Port = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type intermediateConfig struct {
|
||||||
|
Interface interfaceConfig
|
||||||
|
Peer peersConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *intermediateConfig) Export() (*Config, error) {
|
||||||
|
var out Config
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Interface
|
||||||
|
out.Interface, err = v.Interface.Export()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peers
|
||||||
|
peers, ok := v.PeersCount()
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("inconsistent Peer data")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < peers; i++ {
|
||||||
|
p, err := v.ExportPeer(i)
|
||||||
|
if err != nil {
|
||||||
|
err = core.Wrapf(err, "Peer[%v]:", i)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Peer = append(out.Peer, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type interfaceConfig struct {
|
||||||
|
Address netip.Addr
|
||||||
|
PrivateKey string
|
||||||
|
ListenPort uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p interfaceConfig) Export() (InterfaceConfig, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
out := InterfaceConfig{
|
||||||
|
Address: p.Address,
|
||||||
|
ListenPort: p.ListenPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
out.PrivateKey, err = BinaryKeyFromBase64(p.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
err = core.Wrap(err, "PrivateKey")
|
||||||
|
return InterfaceConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type peersConfig struct {
|
||||||
|
PublicKey []string
|
||||||
|
Endpoint []string
|
||||||
|
AllowedIPs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *intermediateConfig) ExportPeer(i int) (PeerConfig, error) {
|
||||||
|
var out PeerConfig
|
||||||
|
|
||||||
|
// Endpoint
|
||||||
|
s := v.Peer.Endpoint[i]
|
||||||
|
err := out.Endpoint.FromString(s)
|
||||||
|
if err != nil {
|
||||||
|
err = core.Wrap(err, "Endpoint")
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey
|
||||||
|
out.PublicKey, err = BinaryKeyFromBase64(v.Peer.PublicKey[i])
|
||||||
|
if err != nil {
|
||||||
|
err = core.Wrap(err, "PublicKey")
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedIPs
|
||||||
|
s = v.Peer.AllowedIPs[i]
|
||||||
|
out.AllowedIPs, err = parseAllowedIPs(s)
|
||||||
|
if err != nil {
|
||||||
|
err = core.Wrap(err, "AllowedIPs")
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAllowedIPs(data string) ([]netip.Prefix, error) {
|
||||||
|
var out []netip.Prefix
|
||||||
|
|
||||||
|
for _, s := range strings.Split(data, ",") {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
p, err := netip.ParsePrefix(s)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *intermediateConfig) PeersCount() (int, bool) {
|
||||||
|
c0 := len(v.Peer.Endpoint)
|
||||||
|
c1 := len(v.Peer.PublicKey)
|
||||||
|
c2 := len(v.Peer.AllowedIPs)
|
||||||
|
|
||||||
|
if c0 != c1 || c1 != c2 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigFromReader parses a wgN.conf file
|
||||||
|
func NewConfigFromReader(r io.Reader) (*Config, error) {
|
||||||
|
temp := &intermediateConfig{}
|
||||||
|
|
||||||
|
if err := gcfg.ReadInto(temp, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp.Export()
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BinaryKey is a binary blob
|
||||||
|
type BinaryKey []byte
|
||||||
|
|
||||||
|
func (k BinaryKey) String() string {
|
||||||
|
return base64.StdEncoding.EncodeToString(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero tells if the key hasn't been set
|
||||||
|
func (k BinaryKey) IsZero() bool {
|
||||||
|
return len(k) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal checks if two keys are identical
|
||||||
|
func (k BinaryKey) Equal(alter BinaryKey) bool {
|
||||||
|
return bytes.Equal(k, alter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BinaryKeyFromBase64 decodes a base64-based string into
|
||||||
|
// a [BinaryKey]
|
||||||
|
func BinaryKeyFromBase64(data string) (BinaryKey, error) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(data)
|
||||||
|
return BinaryKey(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyPair holds a Key pair
|
||||||
|
type KeyPair struct {
|
||||||
|
PrivateKey BinaryKey
|
||||||
|
PublicKey BinaryKey
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// Package wireguard deals with wireguard config
|
||||||
|
package wireguard
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/netip"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Machine is a machine on a Zone
|
||||||
|
type Machine struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
zone *Zone
|
||||||
|
id int
|
||||||
|
Name string `toml:"name"`
|
||||||
|
|
||||||
|
PublicAddresses []netip.Addr `toml:"public,omitempty"`
|
||||||
|
RingAddresses []*RingInfo `toml:"rings,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) String() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID return the index within the [Zone] associated to this [Machine]
|
||||||
|
func (m *Machine) ID() int {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.id == 0 {
|
||||||
|
zoneName := m.zone.Name
|
||||||
|
|
||||||
|
s := m.Name[len(zoneName)+1:]
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(s, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.id = int(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullName returns the Name of the machine including domain name
|
||||||
|
func (m *Machine) FullName() string {
|
||||||
|
if domain := m.zone.zones.domain; domain != "" {
|
||||||
|
var s = []string{
|
||||||
|
m.Name,
|
||||||
|
domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(s, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile reads a file from the machine's config directory
|
||||||
|
func (m *Machine) ReadFile(name string, args ...any) ([]byte, error) {
|
||||||
|
base := m.zone.zones.dir
|
||||||
|
fullName := m.getFilename(name, args...)
|
||||||
|
|
||||||
|
return fs.ReadFile(base, fullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) getFilename(name string, args ...any) string {
|
||||||
|
if len(args) > 0 {
|
||||||
|
name = fmt.Sprintf(name, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := []string{
|
||||||
|
m.zone.Name,
|
||||||
|
m.Name,
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) getPeerByName(name string) (*Machine, bool) {
|
||||||
|
return m.zone.zones.GetMachineByName(name)
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"darvaza.org/core"
|
||||||
|
|
||||||
|
"git.jpi.io/amery/jpictl/pkg/wireguard"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetWireguardConfig reads a wgN.conf file
|
||||||
|
func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) {
|
||||||
|
data, err := m.ReadFile("wg%v.conf", ring)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
return wireguard.NewConfigFromReader(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) tryApplyWireguardConfig(ring int) error {
|
||||||
|
wg, err := m.GetWireguardConfig(ring)
|
||||||
|
switch {
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
return nil
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return m.applyWireguardConfig(ring, wg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) applyWireguardConfig(ring int, wg *wireguard.Config) error {
|
||||||
|
addr := wg.GetAddress()
|
||||||
|
zoneID, nodeID, ok := Rings[ring].Decode(addr)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: invalid wg%v address: %s", m.Name, ring, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.applyZoneNodeID(zoneID, nodeID); err != nil {
|
||||||
|
err = core.Wrapf(err, "%s: wg%v:%s", m.Name, ring, addr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.applyWireguardInterfaceConfig(ring, wg.Interface); err != nil {
|
||||||
|
err = core.Wrapf(err, "%s: wg%v:%s", m.Name, ring, addr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, peer := range wg.Peer {
|
||||||
|
if err := m.applyWireguardPeerConfig(ring, peer); err != nil {
|
||||||
|
err = core.Wrapf(err, "%s: wg%v:%s", m.Name, ring, addr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) applyRingInfo(ring int, new *RingInfo) error {
|
||||||
|
var cur *RingInfo
|
||||||
|
|
||||||
|
for _, ri := range m.RingAddresses {
|
||||||
|
if ri.Ring == ring {
|
||||||
|
cur = ri
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cur == nil {
|
||||||
|
// first, append
|
||||||
|
m.RingAddresses = append(m.RingAddresses, new)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extra, merge
|
||||||
|
return cur.Merge(new)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) applyWireguardInterfaceConfig(ring int, data wireguard.InterfaceConfig) error {
|
||||||
|
ri := &RingInfo{
|
||||||
|
Ring: ring,
|
||||||
|
Enabled: true,
|
||||||
|
Address: data.Address,
|
||||||
|
Keys: &wireguard.KeyPair{
|
||||||
|
PrivateKey: data.PrivateKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.applyRingInfo(ring, ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) error {
|
||||||
|
peer, found := m.getPeerByName(pc.Endpoint.Name())
|
||||||
|
switch {
|
||||||
|
case !found:
|
||||||
|
// unknown
|
||||||
|
case ring == 1 && m.zone != peer.zone:
|
||||||
|
// invalid zone
|
||||||
|
default:
|
||||||
|
// apply RingInfo
|
||||||
|
ri := &RingInfo{
|
||||||
|
Ring: ring,
|
||||||
|
Enabled: true,
|
||||||
|
Keys: &wireguard.KeyPair{
|
||||||
|
PublicKey: pc.PublicKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer.applyRingInfo(ring, ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%q: invalid peer endpoint", pc.Endpoint.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) applyZoneNodeID(zoneID, nodeID int) error {
|
||||||
|
switch {
|
||||||
|
case zoneID == 0:
|
||||||
|
return fmt.Errorf("invalid %s", "zoneID")
|
||||||
|
case nodeID == 0:
|
||||||
|
return fmt.Errorf("invalid %s", "nodeID")
|
||||||
|
case m.ID() != nodeID:
|
||||||
|
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.ID(), nodeID)
|
||||||
|
case m.zone.ID != 0 && m.zone.ID != zoneID:
|
||||||
|
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.zone.ID, zoneID)
|
||||||
|
case m.zone.ID == 0:
|
||||||
|
m.zone.ID = zoneID
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Machine) lookupNetIP() ([]netip.Addr, error) {
|
||||||
|
timeout := 2 * time.Second
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return m.zone.zones.resolver.LookupNetIP(ctx, "ip", m.FullName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) updatePublicAddresses() error {
|
||||||
|
addrs, err := m.lookupNetIP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.PublicAddresses = addrs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) scan() error {
|
||||||
|
for i := 0; i < RingsCount; i++ {
|
||||||
|
if err := m.tryApplyWireguardConfig(i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.updatePublicAddresses()
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"git.jpi.io/amery/jpictl/pkg/wireguard"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxZoneID indicates the highest ID allowed for a Zone
|
||||||
|
MaxZoneID = 0xf
|
||||||
|
// MaxNodeID indicates the highest Machine ID allowed within a Zone
|
||||||
|
MaxNodeID = 0xff - 1
|
||||||
|
// RingsCount indicates how many wireguard rings we have
|
||||||
|
RingsCount = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// RingInfo contains represents the Wireguard endpoint details
|
||||||
|
// for a Machine on a particular ring
|
||||||
|
type RingInfo struct {
|
||||||
|
Ring int `toml:"ring"`
|
||||||
|
Enabled bool `toml:"enabled,omitempty"`
|
||||||
|
Keys *wireguard.KeyPair `toml:"keys,omitempty"`
|
||||||
|
Address netip.Addr `toml:"address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge attempts to combine two RingInfo structs
|
||||||
|
func (ri *RingInfo) Merge(alter *RingInfo) error {
|
||||||
|
switch {
|
||||||
|
case ri.Ring != alter.Ring:
|
||||||
|
// different ring
|
||||||
|
return fmt.Errorf("invalid %s: %v ≠ %v", "ring", ri.Ring, alter.Ring)
|
||||||
|
case ri.Enabled != alter.Enabled:
|
||||||
|
// different state
|
||||||
|
return fmt.Errorf("invalid %s: %v ≠ %v", "enabled", ri.Enabled, alter.Enabled)
|
||||||
|
case !canMergeAddress(ri.Address, alter.Address):
|
||||||
|
// different address
|
||||||
|
return fmt.Errorf("invalid %s: %v ≠ %v", "address", ri.Address, alter.Address)
|
||||||
|
case !canMergeKeyPairs(ri.Keys, alter.Keys):
|
||||||
|
// incompatible keypairs
|
||||||
|
return fmt.Errorf("invalid %s: %s ≠ %s", "keys", ri.Keys, alter.Keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ri.Keys == nil:
|
||||||
|
// assign keypair
|
||||||
|
ri.Keys = alter.Keys
|
||||||
|
case alter.Keys != nil:
|
||||||
|
// fill the gaps on our keypair
|
||||||
|
if ri.Keys.PrivateKey.IsZero() {
|
||||||
|
ri.Keys.PrivateKey = alter.Keys.PrivateKey
|
||||||
|
}
|
||||||
|
if ri.Keys.PublicKey.IsZero() {
|
||||||
|
ri.Keys.PublicKey = alter.Keys.PublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addressEqual(ri.Address, netip.Addr{}) {
|
||||||
|
// assign address
|
||||||
|
ri.Address = alter.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func canMergeAddress(ip1, ip2 netip.Addr) bool {
|
||||||
|
var zero netip.Addr
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case addressEqual(ip1, zero) || addressEqual(ip2, zero) || addressEqual(ip1, ip2):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addressEqual(ip1, ip2 netip.Addr) bool {
|
||||||
|
return ip1.Compare(ip2) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func canMergeKeyPairs(p1, p2 *wireguard.KeyPair) bool {
|
||||||
|
switch {
|
||||||
|
case p1 == nil || p2 == nil:
|
||||||
|
return true
|
||||||
|
case !p1.PrivateKey.IsZero() && !p2.PrivateKey.IsZero() && !p1.PrivateKey.Equal(p2.PrivateKey):
|
||||||
|
return false
|
||||||
|
case !p1.PublicKey.IsZero() && !p2.PublicKey.IsZero() && !p1.PublicKey.Equal(p2.PublicKey):
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RingAddressEncoder provides encoder/decoder access for a particular
|
||||||
|
// Wireguard ring
|
||||||
|
type RingAddressEncoder struct {
|
||||||
|
ID int
|
||||||
|
Encode func(zoneID, nodeID int) (netip.Addr, bool)
|
||||||
|
Decode func(addr netip.Addr) (zoneID, nodeID int, ok bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RingZero is a wg0 address encoder/decoder
|
||||||
|
RingZero = RingAddressEncoder{
|
||||||
|
ID: 0,
|
||||||
|
Decode: ParseRingZeroAddress,
|
||||||
|
Encode: RingZeroAddress,
|
||||||
|
}
|
||||||
|
// RingOne is a wg1 address encoder/decoder
|
||||||
|
RingOne = RingAddressEncoder{
|
||||||
|
ID: 1,
|
||||||
|
Decode: ParseRingOneAddress,
|
||||||
|
Encode: RingOneAddress,
|
||||||
|
}
|
||||||
|
// Rings provides indexed access to the ring address encoders
|
||||||
|
Rings = [RingsCount]RingAddressEncoder{
|
||||||
|
RingZero,
|
||||||
|
RingOne,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidZoneID checks if the given zoneID is a valid 4 bit zone number.
|
||||||
|
//
|
||||||
|
// 0 is reserved, and only allowed when composing CIDRs.
|
||||||
|
func ValidZoneID(zoneID int) bool {
|
||||||
|
switch {
|
||||||
|
case zoneID < 0 || zoneID > MaxZoneID:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidNodeID checks if the given nodeID is a valid 8 bit number.
|
||||||
|
// nodeID is unique within a Zone.
|
||||||
|
// 0 is reserved, and only allowed when composing CIDRs.
|
||||||
|
func ValidNodeID(nodeID int) bool {
|
||||||
|
switch {
|
||||||
|
case nodeID < 0 || nodeID > MaxNodeID:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRingZeroAddress extracts zone and node ID from a wg0 [netip.Addr]
|
||||||
|
// wg0 addresses are of the form `10.0.{{zoneID}}.{{nodeID}}`
|
||||||
|
func ParseRingZeroAddress(addr netip.Addr) (zoneID int, nodeID int, ok bool) {
|
||||||
|
if addr.IsValid() {
|
||||||
|
a4 := addr.As4()
|
||||||
|
|
||||||
|
if a4[0] == 10 && a4[1] == 0 {
|
||||||
|
return int(a4[2]), int(a4[3]), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RingZeroAddress returns a wg0 IP address
|
||||||
|
func RingZeroAddress(zoneID, nodeID int) (netip.Addr, bool) {
|
||||||
|
switch {
|
||||||
|
case !ValidZoneID(zoneID) || !ValidNodeID(nodeID):
|
||||||
|
return netip.Addr{}, false
|
||||||
|
default:
|
||||||
|
a4 := [4]uint8{10, 0, uint8(zoneID), uint8(nodeID)}
|
||||||
|
return netip.AddrFrom4(a4), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRingOneAddress extracts zone and node ID from a wg1 [netip.Addr]
|
||||||
|
// wg1 addresses are of the form `10.{{zoneID << 4}}.{{nodeID}}`
|
||||||
|
func ParseRingOneAddress(addr netip.Addr) (zoneID int, nodeID int, ok bool) {
|
||||||
|
if addr.IsValid() {
|
||||||
|
a4 := addr.As4()
|
||||||
|
|
||||||
|
if a4[0] == 10 && a4[2] == 0 {
|
||||||
|
zoneID = int(a4[1] >> 4)
|
||||||
|
nodeID = int(a4[3])
|
||||||
|
return zoneID, nodeID, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RingOneAddress returns a wg1 IP address
|
||||||
|
func RingOneAddress(zoneID, nodeID int) (netip.Addr, bool) {
|
||||||
|
switch {
|
||||||
|
case !ValidZoneID(zoneID) || !ValidNodeID(nodeID):
|
||||||
|
return netip.Addr{}, false
|
||||||
|
default:
|
||||||
|
a4 := [4]uint8{10, 0, uint8(zoneID << 4), uint8(nodeID)}
|
||||||
|
return netip.AddrFrom4(a4), true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Zones) scan() error {
|
||||||
|
for _, fn := range []func() error{
|
||||||
|
m.scanDirectory,
|
||||||
|
m.scanMachines,
|
||||||
|
m.scanZoneIDs,
|
||||||
|
m.scanSort,
|
||||||
|
} {
|
||||||
|
if err := fn(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Zones) scanDirectory() error {
|
||||||
|
// each directory is a zone
|
||||||
|
entries, err := fs.ReadDir(m.dir, ".")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
z := &Zone{
|
||||||
|
zones: m,
|
||||||
|
Name: e.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := z.scan(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Zones = append(m.Zones, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Zones) scanMachines() error {
|
||||||
|
var err error
|
||||||
|
m.ForEachMachine(func(p *Machine) bool {
|
||||||
|
err = p.scan()
|
||||||
|
return err != nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Zones) scanZoneIDs() error {
|
||||||
|
var hasMissing bool
|
||||||
|
var lastZoneID int
|
||||||
|
|
||||||
|
m.ForEachZone(func(z *Zone) bool {
|
||||||
|
switch {
|
||||||
|
case z.ID == 0:
|
||||||
|
hasMissing = true
|
||||||
|
case z.ID > lastZoneID:
|
||||||
|
lastZoneID = z.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if hasMissing {
|
||||||
|
next := lastZoneID + 1
|
||||||
|
|
||||||
|
m.ForEachZone(func(z *Zone) bool {
|
||||||
|
if z.ID == 0 {
|
||||||
|
z.ID, next = next, next+1
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Zones) scanSort() error {
|
||||||
|
sort.SliceStable(m.Zones, func(i, j int) bool {
|
||||||
|
id1 := m.Zones[i].ID
|
||||||
|
id2 := m.Zones[j].ID
|
||||||
|
return id1 < id2
|
||||||
|
})
|
||||||
|
|
||||||
|
m.ForEachZone(func(z *Zone) bool {
|
||||||
|
sort.SliceStable(z.Machines, func(i, j int) bool {
|
||||||
|
id1 := z.Machines[i].ID()
|
||||||
|
id2 := z.Machines[j].ID()
|
||||||
|
return id1 < id2
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
m.ForEachMachine(func(p *Machine) bool {
|
||||||
|
sort.SliceStable(p.RingAddresses, func(i, j int) bool {
|
||||||
|
ri1 := p.RingAddresses[i]
|
||||||
|
ri2 := p.RingAddresses[j]
|
||||||
|
|
||||||
|
return ri1.Ring < ri2.Ring
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zone) scan() error {
|
||||||
|
// each directory is a machine
|
||||||
|
entries, err := fs.ReadDir(z.zones.dir, z.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
m := &Machine{
|
||||||
|
zone: z,
|
||||||
|
Name: e.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
z.Machines = append(z.Machines, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// Package zones contains information about the cluster
|
||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"darvaza.org/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Zone represents one zone in a cluster
|
||||||
|
type Zone struct {
|
||||||
|
zones *Zones
|
||||||
|
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Machines []*Machine `toml:"machines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zone) String() string {
|
||||||
|
return z.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zones represents all zones in a cluster
|
||||||
|
type Zones struct {
|
||||||
|
dir fs.FS
|
||||||
|
resolver resolver.Resolver
|
||||||
|
domain string
|
||||||
|
|
||||||
|
Zones []*Zone `toml:"zones"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEachMachine calls a function for each Machine in the cluster
|
||||||
|
func (m *Zones) ForEachMachine(fn func(*Machine) bool) {
|
||||||
|
for _, z := range m.Zones {
|
||||||
|
for _, p := range z.Machines {
|
||||||
|
if fn(p) {
|
||||||
|
// terminate
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEachZone calls a function for each Zone in the cluster
|
||||||
|
func (m *Zones) ForEachZone(fn func(*Zone) bool) {
|
||||||
|
for _, p := range m.Zones {
|
||||||
|
if fn(p) {
|
||||||
|
// terminate
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMachineByName looks for a machine with the specified
|
||||||
|
// name on any zone
|
||||||
|
func (m *Zones) GetMachineByName(name string) (*Machine, bool) {
|
||||||
|
var out *Machine
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
m.ForEachMachine(func(p *Machine) bool {
|
||||||
|
if p.Name == name {
|
||||||
|
out = p
|
||||||
|
}
|
||||||
|
|
||||||
|
return out != nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, out != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFS builds a [Zones] tree using the given directory
|
||||||
|
func NewFS(dir fs.FS, domain string) (*Zones, error) {
|
||||||
|
lockuper := resolver.NewCloudflareLookuper()
|
||||||
|
|
||||||
|
z := &Zones{
|
||||||
|
dir: dir,
|
||||||
|
resolver: resolver.NewResolver(lockuper),
|
||||||
|
domain: domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := z.scan(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return z, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New builds a [Zones] tree using the given directory
|
||||||
|
func New(dir, domain string) (*Zones, error) {
|
||||||
|
return NewFS(os.DirFS(dir), domain)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user