Compare commits

2 Commits

Author SHA1 Message Date
2b27cc57e3 render context: add asset_download_token 2025-07-27 12:40:01 +02:00
7f1193cdda dlset: allow globs in name, short kind 2025-07-27 12:08:45 +02:00
21 changed files with 127 additions and 265 deletions

View File

@ -1,6 +1,6 @@
from novit.tech/direktil/dkl:bbea9b9 as dkl from novit.tech/direktil/dkl:bbea9b9 as dkl
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
from golang:1.25.5-trixie as build from golang:1.24.4-bookworm as build
run apt-get update && apt-get install -y git run apt-get update && apt-get install -y git
@ -22,13 +22,13 @@ run \
hack/build ./... hack/build ./...
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
from debian:trixie from debian:bookworm
entrypoint ["/bin/dkl-local-server"] entrypoint ["/bin/dkl-local-server"]
env _uncache=1 env _uncache=1
run apt-get update \ run apt-get update \
&& yes |apt-get install -y genisoimage gdisk dosfstools util-linux udev binutils systemd \ && yes |apt-get install -y genisoimage gdisk dosfstools util-linux udev binutils systemd \
grub2 grub-pc-bin grub-efi-amd64-bin ca-certificates curl openssh-client qemu-utils wireguard-tools \ grub2 grub-pc-bin grub-efi-amd64-bin ca-certificates curl openssh-client qemu-utils \
&& apt-get clean && apt-get clean
copy --from=dkl /bin/dkl /bin/dls /bin/ copy --from=dkl /bin/dkl /bin/dls /bin/

View File

@ -1,12 +1,10 @@
package main package main
import ( import (
"encoding/json"
"flag" "flag"
"io/fs" "io/fs"
"log" "log"
"os" "os"
"path"
"path/filepath" "path/filepath"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
@ -129,22 +127,12 @@ func main() {
defer out.Close() defer out.Close()
switch ext := path.Ext(*outPath); ext { out.Write([]byte("# dkl-dir2config " + Version + "\n"))
case ".yaml":
out.Write([]byte("# dkl-dir2config " + Version + "\n"))
if err = yaml.NewEncoder(out).Encode(dst); err != nil { if err = yaml.NewEncoder(out).Encode(dst); err != nil {
log.Fatal("failed to render output: ", err) log.Fatal("failed to render output: ", err)
}
case ".json":
if err = json.NewEncoder(out).Encode(dst); err != nil {
log.Fatal("failed to render output: ", err)
}
default:
log.Fatal("unknown output file extension: ", ext)
} }
} }
func cfgPath(subPath string) string { return filepath.Join(*dir, subPath) } func cfgPath(subPath string) string { return filepath.Join(*dir, subPath) }

View File

@ -8,7 +8,6 @@ import (
"math/rand" "math/rand"
"path" "path"
"reflect" "reflect"
"strconv"
"strings" "strings"
"github.com/cespare/xxhash" "github.com/cespare/xxhash"
@ -224,7 +223,6 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]any) map[string]any {
if err != nil { if err != nil {
return return
} }
reqJson := cleanJsonObject(buf.String())
key := name key := name
if req.PerHost { if req.PerHost {
@ -237,11 +235,11 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]any) map[string]any {
dir := "/etc/tls/" + name dir := "/etc/tls/" + name
s = fmt.Sprintf("{{ %s %q %q %q %q %q %q %q }}", funcName, s = fmt.Sprintf("{{ %s %q %q %q %q %q %q %q }}", funcName,
dir, cluster, req.CA, key, req.Profile, req.Label, reqJson) dir, cluster, req.CA, key, req.Profile, req.Label, buf.String())
default: default:
s = fmt.Sprintf("{{ %s %q %q %q %q %q %q }}", funcName, s = fmt.Sprintf("{{ %s %q %q %q %q %q %q }}", funcName,
cluster, req.CA, key, req.Profile, req.Label, reqJson) cluster, req.CA, key, req.Profile, req.Label, buf.String())
} }
return return
} }
@ -282,24 +280,16 @@ func (ctx *renderContext) templateFuncs(ctxMap map[string]any) map[string]any {
}, },
"ssh_user_ca": func(path string) (s string) { "ssh_user_ca": func(path string) (s string) {
return fmt.Sprintf("{{ ssh_user_ca %q %q }}", return fmt.Sprintf("{{ ssh_user_ca %q %q}}",
path, cluster) path, cluster)
}, },
"ssh_host_keys": func(dir string) (s string) { "ssh_host_keys": func(dir string) (s string) {
return fmt.Sprintf("{{ ssh_host_keys %q %q \"\" }}", return fmt.Sprintf("{{ ssh_host_keys %q %q \"\"}}",
dir, cluster) dir, cluster)
}, },
"host_download_token": func() (s string) { "host_download_token": func() (s string) {
return "{{ host_download_token }}" return "{{ host_download_token }}"
}, },
"asset_download_token": func(args ...string) (s string) {
argsStr := new(strings.Builder)
for _, arg := range args {
argsStr.WriteByte(' ')
argsStr.WriteString(strconv.Quote(arg))
}
return "{{ asset_download_token" + argsStr.String() + " }}"
},
"hosts_of_group": func() (hosts []any) { "hosts_of_group": func() (hosts []any) {
hosts = make([]any, 0) hosts = make([]any, 0)

View File

@ -29,7 +29,7 @@ func (ctx *renderContext) renderStaticPods() (pods []namePod) {
for n := 0; ; n++ { for n := 0; ; n++ {
str := buf.String() str := buf.String()
podMap := map[string]any{} podMap := map[string]interface{}{}
err := dec.Decode(podMap) err := dec.Decode(podMap)
if err == io.EOF { if err == io.EOF {
@ -46,7 +46,7 @@ func (ctx *renderContext) renderStaticPods() (pods []namePod) {
log.Fatalf("static pod %d: no metadata\n%s", n, buf.String()) log.Fatalf("static pod %d: no metadata\n%s", n, buf.String())
} }
md := podMap["metadata"].(map[any]any) md := podMap["metadata"].(map[interface{}]interface{})
namespace := md["namespace"].(string) namespace := md["namespace"].(string)
name := md["name"].(string) name := md["name"].(string)

View File

@ -1,15 +0,0 @@
package main
import (
"encoding/json"
"fmt"
)
func cleanJsonObject(raw string) string {
v := map[string]any{}
if err := json.Unmarshal([]byte(raw), &v); err != nil {
panic(fmt.Errorf("invalid json: %w\n%s", err, raw))
}
clean, _ := json.Marshal(v)
return string(clean)
}

View File

@ -81,7 +81,7 @@ func buildInitrd(out io.Writer, ctx *renderContext) (err error) {
cat.AppendDir("/etc/ssh", 0o700) cat.AppendDir("/etc/ssh", 0o700)
// XXX do we want bootstrap-stage keys instead of the real host key? // XXX do we want bootstrap-stage keys instead of the real host key?
for _, format := range []string{"rsa", "ecdsa", "ed25519"} { for _, format := range []string{"rsa", "dsa", "ecdsa", "ed25519"} {
keyPath := "/etc/ssh/ssh_host_" + format + "_key" keyPath := "/etc/ssh/ssh_host_" + format + "_key"
cat.AppendBytes(cfg.FileContent(keyPath), keyPath, 0o600) cat.AppendBytes(cfg.FileContent(keyPath), keyPath, 0o600)
} }

View File

@ -60,14 +60,6 @@ func templateFuncs(sslCfg *cfsslconfig.Config) map[string]any {
return map[string]any{ return map[string]any{
"quote": strconv.Quote, "quote": strconv.Quote,
"yaml": asYaml,
"indent": func(s, indent string) string {
buf := new(strings.Builder)
for _, line := range strings.Split(s, "\n") {
buf.WriteString(indent + line + "\n")
}
return buf.String()
},
"password": func(cluster, name, hashAlg string) (password string, err error) { "password": func(cluster, name, hashAlg string) (password string, err error) {
key := cluster + "/" + name key := cluster + "/" + name
@ -178,10 +170,40 @@ func templateFuncs(sslCfg *cfsslconfig.Config) map[string]any {
s = string(kc.Cert) s = string(kc.Cert)
return return
}, },
"tls_dir": func(dir, cluster, caName, name, profile, label, reqJson string) (s string, err error) {
ca, err := getUsableClusterCA(cluster, caName)
if err != nil {
return
}
kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson)
if err != nil {
return
}
return asYaml([]config.FileDef{
{
Path: path.Join(dir, "ca.crt"),
Mode: 0644,
Content: string(ca.Cert),
},
{
Path: path.Join(dir, "tls.crt"),
Mode: 0644,
Content: string(kc.Cert),
},
{
Path: path.Join(dir, "tls.key"),
Mode: 0600,
Content: string(kc.Key),
},
})
},
} }
} }
func asYaml(v any) (string, error) { func asYaml(v interface{}) (string, error) {
ba, err := yaml.Marshal(v) ba, err := yaml.Marshal(v)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -1,15 +0,0 @@
package main
func htmlHeader(title string) string {
return `<!doctype html>
<html>
<head>
<title>` + title + `</title>
<style>@import url('/ui/style.css');@import url('/ui/app.css');</style>
</head>
<body><h1>` + title + `</h1>
`
}
var htmlFooter = `</body>
</html>`

View File

@ -5,7 +5,6 @@ import (
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -14,12 +13,10 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings"
"text/template" "text/template"
"time" "time"
cfsslconfig "github.com/cloudflare/cfssl/config" cfsslconfig "github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@ -168,21 +165,6 @@ func (ctx *renderContext) Tag() (string, error) {
func (ctx *renderContext) TemplateFuncs() map[string]any { func (ctx *renderContext) TemplateFuncs() map[string]any {
funcs := templateFuncs(ctx.SSLConfig) funcs := templateFuncs(ctx.SSLConfig)
// FIXME duplicate from cluster-render-context
getKeyCert := func(cluster, caName, name, profile, label, reqJson string) (kc KeyCert, err error) {
certReq := &csr.CertificateRequest{
KeyRequest: csr.NewKeyRequest(),
}
err = json.Unmarshal([]byte(reqJson), certReq)
if err != nil {
log.Print("CSR unmarshal failed on: ", reqJson)
return
}
return getUsableKeyCert(cluster, caName, name, profile, label, certReq, ctx.SSLConfig)
}
for name, method := range map[string]any{ for name, method := range map[string]any{
"host_ip": func() (s string) { "host_ip": func() (s string) {
return ctx.Host.IPs[0] return ctx.Host.IPs[0]
@ -195,39 +177,6 @@ func (ctx *renderContext) TemplateFuncs() map[string]any {
return hex.EncodeToString(ba[:]) return hex.EncodeToString(ba[:])
}, },
"tls_dir": func(dir, cluster, caName, name, profile, label, reqJson string) (s string, err error) {
ca, err := getUsableClusterCA(cluster, caName)
if err != nil {
return
}
reqJson = strings.ReplaceAll(reqJson, "${host_ip}", ctx.Host.IPs[0])
reqJson = strings.ReplaceAll(reqJson, "${host_name}", ctx.Host.Name)
kc, err := getKeyCert(cluster, caName, name, profile, label, reqJson)
if err != nil {
return
}
return asYaml([]config.FileDef{
{
Path: path.Join(dir, "ca.crt"),
Mode: 0644,
Content: string(ca.Cert),
},
{
Path: path.Join(dir, "tls.crt"),
Mode: 0644,
Content: string(kc.Cert),
},
{
Path: path.Join(dir, "tls.key"),
Mode: 0600,
Content: string(kc.Key),
},
})
},
"ssh_user_ca": func(path, cluster string) (s string, err error) { "ssh_user_ca": func(path, cluster string) (s string, err error) {
userCA, err := sshCAPubKey(cluster) userCA, err := sshCAPubKey(cluster)
return asYaml([]config.FileDef{{ return asYaml([]config.FileDef{{
@ -317,20 +266,6 @@ func (ctx *renderContext) TemplateFuncs() map[string]any {
return return
}, },
"wg_key": func(name string) (key string, err error) {
return wgKey(name + "/hosts/" + ctx.Host.Name)
},
"wg_psk": func(name, peerName string) (key string, err error) {
a := ctx.Host.Name
b := peerName
if a > b {
a, b = b, a
}
return wgKey(name + "/psks/" + a + " " + b)
},
"wg_pubkey": func(name, host string) (key string, err error) {
return wgKey(name + "/hosts/" + host)
},
} { } {
funcs[name] = method funcs[name] = method
} }

View File

@ -32,6 +32,7 @@ func getSSHKeyPairs(host string) (pairs []SSHKeyPair, err error) {
genLoop: genLoop:
for _, keyType := range []string{ for _, keyType := range []string{
"rsa", "rsa",
"dsa",
"ecdsa", "ecdsa",
"ed25519", "ed25519",
} { } {

View File

@ -1,44 +0,0 @@
package main
import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var wgKeys = KVSecrets[string]{"wireguard"}
func wgKey(path string) (key string, err error) {
return wgKeys.GetOrCreate(path, func() (key string, err error) {
k, err := wgtypes.GeneratePrivateKey()
if err != nil {
return
}
key = k.String()
return
})
}
func wgPSKey(path string) (key string, err error) {
return wgKeys.GetOrCreate(path, func() (key string, err error) {
k, err := wgtypes.GenerateKey()
if err != nil {
return
}
key = k.String()
return
})
}
func wgPubKey(path string) (pubkey string, err error) {
key, err := wgKey(path)
if err != nil {
return
}
k, err := wgtypes.ParseKey(key)
if err != nil {
return
}
pubkey = k.PublicKey().String()
return
}

View File

@ -1,32 +1,20 @@
package main package main
import ( import (
"bytes"
"compress/gzip" "compress/gzip"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
restful "github.com/emicklei/go-restful" restful "github.com/emicklei/go-restful"
"gopkg.in/yaml.v2"
"m.cluseau.fr/go/httperr"
"novit.tech/direktil/pkg/localconfig"
) )
func wsUploadConfig(req *restful.Request, resp *restful.Response) { func wsUploadConfig(req *restful.Request, resp *restful.Response) {
cfg := &localconfig.Config{} body := req.Request.Body
if err := req.ReadEntity(cfg); err != nil {
wsError(resp, httperr.BadRequest(err.Error()))
return
}
cfgBytes, err := yaml.Marshal(cfg) err := writeNewConfig(body)
if err != nil { body.Close()
wsError(resp, err)
return
}
err = writeNewConfig(cfgBytes)
if err != nil { if err != nil {
wsError(resp, err) wsError(resp, err)
return return
@ -35,7 +23,7 @@ func wsUploadConfig(req *restful.Request, resp *restful.Response) {
resp.WriteEntity(true) resp.WriteEntity(true)
} }
func writeNewConfig(cfgBytes []byte) (err error) { func writeNewConfig(reader io.Reader) (err error) {
out, err := os.CreateTemp(*dataDir, ".config-upload") out, err := os.CreateTemp(*dataDir, ".config-upload")
if err != nil { if err != nil {
return return
@ -43,7 +31,7 @@ func writeNewConfig(cfgBytes []byte) (err error) {
defer os.Remove(out.Name()) defer os.Remove(out.Name())
_, err = io.Copy(out, bytes.NewReader(cfgBytes)) _, err = io.Copy(out, reader)
out.Close() out.Close()
if err != nil { if err != nil {
return return

View File

@ -17,11 +17,6 @@ import (
"m.cluseau.fr/go/httperr" "m.cluseau.fr/go/httperr"
) )
func globMatch(pattern, value string) bool {
ok, _ := filepath.Match(pattern, value)
return ok
}
type DownloadSet struct { type DownloadSet struct {
Expiry time.Time Expiry time.Time
Items []DownloadSetItem Items []DownloadSetItem
@ -29,7 +24,7 @@ type DownloadSet struct {
func (s DownloadSet) Contains(kind, name, asset string) bool { func (s DownloadSet) Contains(kind, name, asset string) bool {
for _, item := range s.Items { for _, item := range s.Items {
if item.Kind == kind && globMatch(item.Name, name) && if item.Kind == kind && item.Name == name &&
slices.Contains(item.Assets, asset) { slices.Contains(item.Assets, asset) {
return true return true
} }
@ -246,13 +241,28 @@ func wsDownloadSet(req *restful.Request, resp *restful.Response) {
set, err := getDlSet(req) set, err := getDlSet(req)
if err != nil { if err != nil {
resp.WriteHeader(err.Status) resp.WriteHeader(err.Status)
resp.Write([]byte(htmlHeader(err.Error()))) resp.Write([]byte(`<!doctype html>
resp.Write([]byte(htmlFooter)) <html>
<head>
<title>` + err.Error() + `</title>
<style src="/ui/style.css"/>
<style src="/ui/app.css"/>
</head>
<body><h1>` + err.Error() + `</h1></body>
</html>`))
return return
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.WriteString(htmlHeader("Download set")) buf.WriteString(`<!doctype html>
<html>
<head>
<title>Download set</title>
<style src="/ui/style.css"/>
<style src="/ui/app.css"/>
</head>
<body><h1>Download set</h1>
`)
cfg, err2 := readConfig() cfg, err2 := readConfig()
if err2 != nil { if err2 != nil {
@ -265,13 +275,13 @@ func wsDownloadSet(req *restful.Request, resp *restful.Response) {
switch item.Kind { switch item.Kind {
case "cluster": case "cluster":
for _, c := range cfg.Clusters { for _, c := range cfg.Clusters {
if globMatch(item.Name, c.Name) { if ok, _ := filepath.Match(item.Name, c.Name); ok {
names = append(names, c.Name) names = append(names, c.Name)
} }
} }
case "host": case "host":
for _, h := range cfg.Hosts { for _, h := range cfg.Hosts {
if globMatch(item.Name, h.Name) { if ok, _ := filepath.Match(item.Name, h.Name); ok {
names = append(names, h.Name) names = append(names, h.Name)
} }
} }
@ -287,6 +297,6 @@ func wsDownloadSet(req *restful.Request, resp *restful.Response) {
} }
} }
buf.WriteString(htmlFooter) buf.WriteString("</body></html>")
buf.WriteTo(resp) buf.WriteTo(resp)
} }

View File

@ -180,20 +180,34 @@ func wsDownloadPage(req *restful.Request, resp *restful.Response) {
spec, ok := wState.Get().Downloads[token] spec, ok := wState.Get().Downloads[token]
if !ok { if !ok {
resp.WriteHeader(http.StatusNotFound) resp.WriteHeader(http.StatusNotFound)
resp.Write([]byte(htmlHeader("Token not found"))) resp.Write([]byte(`<!doctype html>
resp.Write([]byte(htmlFooter)) <html>
<head>
<title>Token not found</title>
<style src="/ui/style.css"/>
<style src="/ui/app.css"/>
</head>
<body><h1>Token not found</h1></body>
</html>`))
return return
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.WriteString(htmlHeader(fmt.Sprintf("Token assets: %s %s", spec.Kind, spec.Name))) fmt.Fprintf(buf, `<!doctype html>
<html>
<head>
<title>Token assets: %s %s</title>
<style src="/ui/style.css"/>
<style src="/ui/app.css"/>
</head>
<body><h1>Token assets: %s %s</h1>
<ul>
`, spec.Kind, spec.Name, spec.Kind, spec.Name)
buf.WriteString("<ul>")
for _, asset := range spec.Assets { for _, asset := range spec.Assets {
fmt.Fprintf(buf, "<li><a href=\"%s\" download>%s</a></li>\n", asset, asset) fmt.Fprintf(buf, "<li><a href=\"%s\" download>%s</a></li>\n", asset, asset)
} }
buf.WriteString("</ul>")
buf.WriteString(htmlFooter) buf.WriteString("</ul></body></html>")
buf.WriteTo(resp) buf.WriteTo(resp)
} }

View File

@ -28,6 +28,7 @@ func (hft HostFromTemplate) ClusterName(cfg *localconfig.Config) string {
func hostOrTemplate(cfg *localconfig.Config, name string) (host *localconfig.Host) { func hostOrTemplate(cfg *localconfig.Config, name string) (host *localconfig.Host) {
host = cfg.Host(name) host = cfg.Host(name)
if host != nil { if host != nil {
log.Print("no host named ", name)
return return
} }
@ -38,13 +39,13 @@ func hostOrTemplate(cfg *localconfig.Config, name string) (host *localconfig.Hos
} }
if !found { if !found {
log.Print("no host named ", name) log.Print("no host from template named ", name)
return return
} }
ht := cfg.HostTemplate(hft.Template) ht := cfg.HostTemplate(hft.Template)
if ht == nil { if ht == nil {
log.Print("host ", name, " found but no template named ", hft.Template) log.Print("no host template named ", name)
return return
} }

View File

@ -11,7 +11,6 @@ import (
cfsslconfig "github.com/cloudflare/cfssl/config" cfsslconfig "github.com/cloudflare/cfssl/config"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"gopkg.in/yaml.v2"
"m.cluseau.fr/go/httperr" "m.cluseau.fr/go/httperr"
"novit.tech/direktil/pkg/localconfig" "novit.tech/direktil/pkg/localconfig"
@ -76,7 +75,7 @@ func registerWS(rest *restful.Container) {
// - configs API // - configs API
ws.Route(ws.POST("/configs").To(wsUploadConfig). ws.Route(ws.POST("/configs").To(wsUploadConfig).
Consumes(mime.YAML, mime.JSON).Param(ws.BodyParameter("config", "The new full configuration")). Consumes(mime.YAML).Param(ws.BodyParameter("config", "The new full configuration")).
Produces(mime.JSON).Writes(true). Produces(mime.JSON).Writes(true).
Doc("Upload a new current configuration, archiving the previous one")) Doc("Upload a new current configuration, archiving the previous one"))
@ -308,26 +307,3 @@ func wsRender(resp *restful.Response, sslCfg *cfsslconfig.Config, tmplStr string
return return
} }
} }
func init() {
restful.RegisterEntityAccessor(mime.YAML, yamlEntityAccessor{})
}
type yamlEntityAccessor struct{}
var _ restful.EntityReaderWriter = yamlEntityAccessor{}
func (_ yamlEntityAccessor) Read(req *restful.Request, v any) error {
decoder := yaml.NewDecoder(req.Request.Body)
return decoder.Decode(v)
}
func (_ yamlEntityAccessor) Write(resp *restful.Response, status int, v any) error {
if v == nil {
resp.WriteHeader(status)
// do not write a nil representation
return nil
}
resp.Header().Set("Content-Type", mime.YAML)
resp.WriteHeader(status)
return yaml.NewEncoder(resp.ResponseWriter).Encode(v)
}

5
go.mod
View File

@ -11,15 +11,12 @@ require (
github.com/emicklei/go-restful v2.16.0+incompatible github.com/emicklei/go-restful v2.16.0+incompatible
github.com/emicklei/go-restful-openapi v1.4.1 github.com/emicklei/go-restful-openapi v1.4.1
github.com/go-git/go-git/v5 v5.16.2 github.com/go-git/go-git/v5 v5.16.2
github.com/klauspost/compress v1.18.0
github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34 github.com/mcluseau/go-swagger-ui v0.0.0-20191019002626-fd9128c24a34
github.com/miolini/datacounter v1.0.3 github.com/miolini/datacounter v1.0.3
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/pierrec/lz4 v2.6.1+incompatible github.com/pierrec/lz4 v2.6.1+incompatible
github.com/sergeymakinen/go-crypt v1.0.1 github.com/sergeymakinen/go-crypt v1.0.1
golang.org/x/crypto v0.39.0 golang.org/x/crypto v0.39.0
golang.org/x/sys v0.33.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
gopkg.in/src-d/go-billy.v4 v4.3.2 gopkg.in/src-d/go-billy.v4 v4.3.2
gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@ -58,6 +55,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/sqlstruct v0.0.0-20210630145711-dae28ed37023 // indirect github.com/kisielk/sqlstruct v0.0.0-20210630145711-dae28ed37023 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@ -73,6 +71,7 @@ require (
github.com/zmap/zlint/v3 v3.5.0 // indirect github.com/zmap/zlint/v3 v3.5.0 // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect golang.org/x/text v0.26.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect

4
go.sum
View File

@ -3,6 +3,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@ -311,8 +313,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=

View File

@ -1,11 +1,8 @@
#! /bin/bash #! /bin/bash
set -ex set -ex
GIT_TAG=$(git describe --always --dirty)
case "$1" in case "$1" in
commit) tag=$GIT_TAG ;; commit) tag=$(git describe --always --dirty) ;;
"") tag=latest ;; "") tag=latest ;;
*) tag=$1 ;; *) tag=$1 ;;
esac esac
docker build -t novit.tech/direktil/local-server:$tag . --build-arg GIT_TAG=$GIT_TAG docker build -t novit.tech/direktil/local-server:$tag .

View File

@ -1,23 +1,37 @@
#! /bin/sh #! /bin/sh
if [ $# -ne 2 ]; then if [ $# -ne 2 ]; then
echo "USAGE: $0 <device>" echo "USAGE: $0 <device> <base url>"
fi fi
dev=$1 dev=$1
base_url=$2
: ${MP:=/mnt}
set -ex set -ex
zcat boot.img.gz | dd of=$dev mkdir -p $MP
apk add sgdisk
[[ $dev =~ nvme ]] && [[ $dev =~ nvme ]] &&
devp=${dev}p || devp=${dev}p ||
devp=${dev} devp=${dev}
sgdisk --move-second-header --new=3:0:0 $dev if vgdisplay storage; then
# the system is already installed, just upgrade
mount -t vfat ${devp}1 $MP
curl ${base_url}/boot.tar |tar xv -C $MP
umount $MP
pvcreate ${devp}3 else
vgcreate storage ${devp}3 sgdisk --clear $dev
curl ${base_url}/boot.img.lz4 |lz4cat >$dev
sgdisk --move-second-header --new=3:0:0 $dev
pvcreate ${devp}3
vgcreate storage ${devp}3
fi
while umount $MP; do true; done

View File

@ -2,6 +2,7 @@ package clustersconfig
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -177,7 +178,7 @@ type dirStore struct {
// listDir // listDir
func (b *dirStore) listDir(prefix string) (subDirs []string, err error) { func (b *dirStore) listDir(prefix string) (subDirs []string, err error) {
entries, err := os.ReadDir(filepath.Join(b.path, prefix)) entries, err := ioutil.ReadDir(filepath.Join(b.path, prefix))
if err != nil { if err != nil {
return return
} }
@ -225,7 +226,7 @@ func (b *dirStore) List(prefix string) ([]string, error) {
// Load is part of the DataBackend interface // Load is part of the DataBackend interface
func (b *dirStore) Get(key string) (ba []byte, err error) { func (b *dirStore) Get(key string) (ba []byte, err error) {
ba, err = os.ReadFile(filepath.Join(b.path, filepath.Join(path.Split(key))+".yaml")) ba, err = ioutil.ReadFile(filepath.Join(b.path, filepath.Join(path.Split(key))+".yaml"))
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, nil return nil, nil
} }