Move daemon.yml types and functionality out of entrypoint and Env
This commit is contained in:
parent
03618ba72c
commit
08f47bd514
@ -5,6 +5,7 @@ import (
|
|||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
"cryptic-net/admin"
|
"cryptic-net/admin"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/daemon"
|
||||||
"cryptic-net/garage"
|
"cryptic-net/garage"
|
||||||
"cryptic-net/nebula"
|
"cryptic-net/nebula"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@ -54,7 +55,7 @@ var subCmdAdminCreateNetwork = subCmd{
|
|||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
flags := subCmdCtx.flagSet(false)
|
||||||
|
|
||||||
daemonYmlPath := flags.StringP(
|
daemonConfigPath := flags.StringP(
|
||||||
"config-path", "c", "",
|
"config-path", "c", "",
|
||||||
"Optional path to a daemon.yml file to load configuration from.",
|
"Optional path to a daemon.yml file to load configuration from.",
|
||||||
)
|
)
|
||||||
@ -86,7 +87,7 @@ var subCmdAdminCreateNetwork = subCmd{
|
|||||||
env := subCmdCtx.env
|
env := subCmdCtx.env
|
||||||
|
|
||||||
if *dumpConfig {
|
if *dumpConfig {
|
||||||
return writeBuiltinDaemonYml(env, os.Stdout)
|
return daemon.CopyDefaultConfig(os.Stdout, env.AppDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *domain == "" || *subnetStr == "" || *hostName == "" {
|
if *domain == "" || *subnetStr == "" || *hostName == "" {
|
||||||
@ -128,14 +129,13 @@ var subCmdAdminCreateNetwork = subCmd{
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil {
|
daemonConfig, err := daemon.LoadConfig(env.AppDirPath, *daemonConfigPath)
|
||||||
return fmt.Errorf("merging and writing daemon.yml file: %w", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading daemon config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon := env.ThisDaemon()
|
if len(daemonConfig.Storage.Allocations) < 3 {
|
||||||
|
return fmt.Errorf("daemon config with at least 3 allocations was not provided")
|
||||||
if len(daemon.Storage.Allocations) < 3 {
|
|
||||||
return fmt.Errorf("daemon.yml with at least 3 allocations was not provided")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nebulaCACert, err := nebula.NewCACert(*domain, subnet)
|
nebulaCACert, err := nebula.NewCACert(*domain, subnet)
|
||||||
@ -165,16 +165,16 @@ var subCmdAdminCreateNetwork = subCmd{
|
|||||||
GarageGlobalBucketS3APICredentials: garage.NewS3APICredentials(),
|
GarageGlobalBucketS3APICredentials: garage.NewS3APICredentials(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if env, err = mergeDaemonIntoBootstrap(env); err != nil {
|
if env, err = mergeDaemonConfigIntoBootstrap(env, daemonConfig); err != nil {
|
||||||
return fmt.Errorf("merging daemon.yml into bootstrap data: %w", err)
|
return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env)
|
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, daemonConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("generating nebula config: %w", err)
|
return fmt.Errorf("generating nebula config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
garagePmuxProcConfigs, err := garagePmuxProcConfigs(env)
|
garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, daemonConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("generating garage configs: %w", err)
|
return fmt.Errorf("generating garage configs: %w", err)
|
||||||
}
|
}
|
||||||
@ -206,17 +206,17 @@ var subCmdAdminCreateNetwork = subCmd{
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "waiting for garage instances to come online")
|
fmt.Fprintln(os.Stderr, "waiting for garage instances to come online")
|
||||||
if err := waitForGarageAndNebula(ctx, env); err != nil {
|
if err := waitForGarageAndNebula(ctx, env, daemonConfig); err != nil {
|
||||||
return fmt.Errorf("waiting for garage to start up: %w", err)
|
return fmt.Errorf("waiting for garage to start up: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "applying initial garage layout")
|
fmt.Fprintln(os.Stderr, "applying initial garage layout")
|
||||||
if err := garageApplyLayout(ctx, env); err != nil {
|
if err := garageApplyLayout(ctx, env, daemonConfig); err != nil {
|
||||||
return fmt.Errorf("applying initial garage layout: %w", err)
|
return fmt.Errorf("applying initial garage layout: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "initializing garage shared global bucket")
|
fmt.Fprintln(os.Stderr, "initializing garage shared global bucket")
|
||||||
err = garageInitializeGlobalBucket(ctx, env)
|
err = garageInitializeGlobalBucket(ctx, env, daemonConfig)
|
||||||
|
|
||||||
if cErr := (garage.AdminClientError{}); errors.As(err, &cErr) && cErr.StatusCode == 409 {
|
if cErr := (garage.AdminClientError{}); errors.As(err, &cErr) && cErr.StatusCode == 409 {
|
||||||
return fmt.Errorf("shared global bucket has already been created, are the storage allocations from a previously initialized cryptic-net being used?")
|
return fmt.Errorf("shared global bucket has already been created, are the storage allocations from a previously initialized cryptic-net being used?")
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/daemon"
|
||||||
"cryptic-net/garage"
|
"cryptic-net/garage"
|
||||||
|
|
||||||
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
||||||
@ -25,11 +26,8 @@ import (
|
|||||||
// * Creates the data directory and copies the appdir bootstrap file into there,
|
// * Creates the data directory and copies the appdir bootstrap file into there,
|
||||||
// if it's not already there.
|
// if it's not already there.
|
||||||
//
|
//
|
||||||
// * Merges the user-provided daemon.yml file with the default, and writes the
|
// * Merges daemon configuration into the bootstrap configuration, and rewrites
|
||||||
// result to the runtime dir.
|
// the bootstrap file.
|
||||||
//
|
|
||||||
// * Merges daemon.yml configuration into the bootstrap configuration, and
|
|
||||||
// rewrites the bootstrap file.
|
|
||||||
//
|
//
|
||||||
// * Sets up environment variables that all other sub-processes then use, based
|
// * Sets up environment variables that all other sub-processes then use, based
|
||||||
// on the runtime dir.
|
// on the runtime dir.
|
||||||
@ -78,10 +76,13 @@ func reloadBootstrap(env crypticnet.Env, s3Client garage.S3APIClient) (crypticne
|
|||||||
// been canceled or bootstrap info has been changed. This will always block
|
// been canceled or bootstrap info has been changed. This will always block
|
||||||
// until the spawned pmux has returned, and returns a copy of Env with updated
|
// until the spawned pmux has returned, and returns a copy of Env with updated
|
||||||
// boostrap info.
|
// boostrap info.
|
||||||
func runDaemonPmuxOnce(env crypticnet.Env) (crypticnet.Env, error) {
|
func runDaemonPmuxOnce(
|
||||||
|
env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
|
) (
|
||||||
|
crypticnet.Env, error,
|
||||||
|
) {
|
||||||
|
|
||||||
thisHost := env.Bootstrap.ThisHost()
|
thisHost := env.Bootstrap.ThisHost()
|
||||||
thisDaemon := env.ThisDaemon()
|
|
||||||
fmt.Fprintf(os.Stderr, "host name is %q, ip is %q\n", thisHost.Name, thisHost.Nebula.IP)
|
fmt.Fprintf(os.Stderr, "host name is %q, ip is %q\n", thisHost.Name, thisHost.Nebula.IP)
|
||||||
|
|
||||||
// create s3Client anew on every loop, in case the topology has
|
// create s3Client anew on every loop, in case the topology has
|
||||||
@ -89,33 +90,31 @@ func runDaemonPmuxOnce(env crypticnet.Env) (crypticnet.Env, error) {
|
|||||||
// endpoint.
|
// endpoint.
|
||||||
s3Client := env.Bootstrap.GlobalBucketS3APIClient()
|
s3Client := env.Bootstrap.GlobalBucketS3APIClient()
|
||||||
|
|
||||||
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env)
|
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, daemonConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return crypticnet.Env{}, fmt.Errorf("generating nebula config: %w", err)
|
return crypticnet.Env{}, fmt.Errorf("generating nebula config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(env)
|
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(env, daemonConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return crypticnet.Env{}, fmt.Errorf("generating dnsmasq config: %w", err)
|
return crypticnet.Env{}, fmt.Errorf("generating dnsmasq config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pmuxProcConfigs := []pmuxlib.ProcessConfig{
|
garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, daemonConfig)
|
||||||
nebulaPmuxProcConfig,
|
|
||||||
dnsmasqPmuxProcConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(thisDaemon.Storage.Allocations) > 0 {
|
|
||||||
|
|
||||||
garagePmuxProcConfigs, err := garagePmuxProcConfigs(env)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return crypticnet.Env{}, fmt.Errorf("generating garage children configs: %w", err)
|
return crypticnet.Env{}, fmt.Errorf("generating garage children configs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pmuxProcConfigs = append(pmuxProcConfigs, garagePmuxProcConfigs...)
|
pmuxConfig := pmuxlib.Config{
|
||||||
|
Processes: append(
|
||||||
|
[]pmuxlib.ProcessConfig{
|
||||||
|
nebulaPmuxProcConfig,
|
||||||
|
dnsmasqPmuxProcConfig,
|
||||||
|
},
|
||||||
|
garagePmuxProcConfigs...,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
pmuxConfig := pmuxlib.Config{Processes: pmuxProcConfigs}
|
|
||||||
|
|
||||||
doneCh := env.Context.Done()
|
doneCh := env.Context.Done()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@ -134,7 +133,7 @@ func runDaemonPmuxOnce(env crypticnet.Env) (crypticnet.Env, error) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
if err := waitForGarageAndNebula(ctx, env); err != nil {
|
if err := waitForGarageAndNebula(ctx, env, daemonConfig); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
|
fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -151,19 +150,19 @@ func runDaemonPmuxOnce(env crypticnet.Env) (crypticnet.Env, error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if len(thisDaemon.Storage.Allocations) > 0 {
|
if len(daemonConfig.Storage.Allocations) > 0 {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
if err := waitForGarageAndNebula(ctx, env); err != nil {
|
if err := waitForGarageAndNebula(ctx, env, daemonConfig); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
|
fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := doOnce(ctx, func(ctx context.Context) error {
|
err := doOnce(ctx, func(ctx context.Context) error {
|
||||||
fmt.Fprintln(os.Stderr, "applying garage layout")
|
fmt.Fprintln(os.Stderr, "applying garage layout")
|
||||||
return garageApplyLayout(ctx, env)
|
return garageApplyLayout(ctx, env, daemonConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -208,7 +207,7 @@ var subCmdDaemon = subCmd{
|
|||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
flags := subCmdCtx.flagSet(false)
|
||||||
|
|
||||||
daemonYmlPath := flags.StringP(
|
daemonConfigPath := flags.StringP(
|
||||||
"config-path", "c", "",
|
"config-path", "c", "",
|
||||||
"Optional path to a daemon.yml file to load configuration from.",
|
"Optional path to a daemon.yml file to load configuration from.",
|
||||||
)
|
)
|
||||||
@ -230,7 +229,7 @@ var subCmdDaemon = subCmd{
|
|||||||
env := subCmdCtx.env
|
env := subCmdCtx.env
|
||||||
|
|
||||||
if *dumpConfig {
|
if *dumpConfig {
|
||||||
return writeBuiltinDaemonYml(env, os.Stdout)
|
return daemon.CopyDefaultConfig(os.Stdout, env.AppDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimeDirPath := env.RuntimeDirPath
|
runtimeDirPath := env.RuntimeDirPath
|
||||||
@ -285,24 +284,25 @@ var subCmdDaemon = subCmd{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil {
|
daemonConfig, err := daemon.LoadConfig(env.AppDirPath, *daemonConfigPath)
|
||||||
return fmt.Errorf("merging and writing daemon.yml file: %w", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading daemon config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// we update this Host's data using whatever configuration has been
|
// we update this Host's data using whatever configuration has been
|
||||||
// provided by daemon.yml. This way the daemon has the most
|
// provided by the daemon config. This way the daemon has the most
|
||||||
// up-to-date possible bootstrap. This updated bootstrap will later
|
// up-to-date possible bootstrap. This updated bootstrap will later get
|
||||||
// get updated in garage using update-global-bucket, so other hosts
|
// updated in garage using bootstrap.PutGarageBoostrapHost, so other
|
||||||
// will see it as well.
|
// hosts will see it as well.
|
||||||
if env, err = mergeDaemonIntoBootstrap(env); err != nil {
|
if env, err = mergeDaemonConfigIntoBootstrap(env, daemonConfig); err != nil {
|
||||||
return fmt.Errorf("merging daemon.yml into bootstrap data: %w", err)
|
return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
if env, err = runDaemonPmuxOnce(env); errors.Is(err, context.Canceled) {
|
env, err = runDaemonPmuxOnce(env, daemonConfig)
|
||||||
|
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/daemon"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -36,15 +37,18 @@ func copyBootstrapToDataDirAndReload(env crypticnet.Env, r io.Reader) (crypticne
|
|||||||
return env.LoadBootstrap(path)
|
return env.LoadBootstrap(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeDaemonIntoBootstrap(env crypticnet.Env) (crypticnet.Env, error) {
|
func mergeDaemonConfigIntoBootstrap(
|
||||||
daemon := env.ThisDaemon()
|
env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
|
) (
|
||||||
|
crypticnet.Env, error,
|
||||||
|
) {
|
||||||
host := env.Bootstrap.ThisHost()
|
host := env.Bootstrap.ThisHost()
|
||||||
|
|
||||||
host.Nebula.PublicAddr = daemon.VPN.PublicAddr
|
host.Nebula.PublicAddr = daemonConfig.VPN.PublicAddr
|
||||||
|
|
||||||
host.Garage = nil
|
host.Garage = nil
|
||||||
|
|
||||||
if allocs := daemon.Storage.Allocations; len(allocs) > 0 {
|
if allocs := daemonConfig.Storage.Allocations; len(allocs) > 0 {
|
||||||
|
|
||||||
host.Garage = new(bootstrap.GarageHost)
|
host.Garage = new(bootstrap.GarageHost)
|
||||||
|
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
crypticnet "cryptic-net"
|
|
||||||
"cryptic-net/yamlutil"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func builtinDaemonYmlPath(env crypticnet.Env) string {
|
|
||||||
return filepath.Join(env.AppDirPath, "etc", "daemon.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeBuiltinDaemonYml(env crypticnet.Env, w io.Writer) error {
|
|
||||||
|
|
||||||
builtinDaemonYmlPath := builtinDaemonYmlPath(env)
|
|
||||||
|
|
||||||
builtinDaemonYml, err := os.ReadFile(builtinDaemonYmlPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reading default daemon.yml at %q: %w", builtinDaemonYmlPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := w.Write(builtinDaemonYml); err != nil {
|
|
||||||
return fmt.Errorf("writing default daemon.yml: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeMergedDaemonYml(env crypticnet.Env, userDaemonYmlPath string) error {
|
|
||||||
|
|
||||||
builtinDaemonYmlPath := builtinDaemonYmlPath(env)
|
|
||||||
|
|
||||||
var fullDaemonYml map[string]interface{}
|
|
||||||
|
|
||||||
if err := yamlutil.LoadYamlFile(&fullDaemonYml, builtinDaemonYmlPath); err != nil {
|
|
||||||
return fmt.Errorf("parsing builtin daemon.yml file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if userDaemonYmlPath != "" {
|
|
||||||
|
|
||||||
var daemonYml map[string]interface{}
|
|
||||||
if err := yamlutil.LoadYamlFile(&daemonYml, userDaemonYmlPath); err != nil {
|
|
||||||
return fmt.Errorf("parsing %q: %w", userDaemonYmlPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mergo.Merge(&fullDaemonYml, daemonYml, mergo.WithOverride)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("merging contents of file %q: %w", userDaemonYmlPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fullDaemonYmlB, err := yaml.Marshal(fullDaemonYml)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("yaml marshaling daemon config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
daemonYmlPath := filepath.Join(env.RuntimeDirPath, "daemon.yml")
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(daemonYmlPath, fullDaemonYmlB, 0400); err != nil {
|
|
||||||
return fmt.Errorf("writing daemon.yml file to %q: %w", daemonYmlPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/daemon"
|
||||||
"cryptic-net/dnsmasq"
|
"cryptic-net/dnsmasq"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -11,9 +12,11 @@ import (
|
|||||||
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsmasqPmuxProcConfig(env crypticnet.Env) (pmuxlib.ProcessConfig, error) {
|
func dnsmasqPmuxProcConfig(
|
||||||
|
env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
thisDaemon := env.ThisDaemon()
|
) (
|
||||||
|
pmuxlib.ProcessConfig, error,
|
||||||
|
) {
|
||||||
|
|
||||||
confPath := filepath.Join(env.RuntimeDirPath, "dnsmasq.conf")
|
confPath := filepath.Join(env.RuntimeDirPath, "dnsmasq.conf")
|
||||||
|
|
||||||
@ -27,7 +30,7 @@ func dnsmasqPmuxProcConfig(env crypticnet.Env) (pmuxlib.ProcessConfig, error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
confData := dnsmasq.ConfData{
|
confData := dnsmasq.ConfData{
|
||||||
Resolvers: thisDaemon.DNS.Resolvers,
|
Resolvers: daemonConfig.DNS.Resolvers,
|
||||||
Domain: env.Bootstrap.AdminCreationParams.Domain,
|
Domain: env.Bootstrap.AdminCreationParams.Domain,
|
||||||
IP: env.Bootstrap.ThisHost().Nebula.IP,
|
IP: env.Bootstrap.ThisHost().Nebula.IP,
|
||||||
Hosts: hostsSlice,
|
Hosts: hostsSlice,
|
||||||
|
@ -52,7 +52,7 @@ var subCmdGarageMC = subCmd{
|
|||||||
args = append([]string{"mc"}, args...)
|
args = append([]string{"mc"}, args...)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
binPath = env.BinPath("mc")
|
binPath = "mc"
|
||||||
cliEnv = append(
|
cliEnv = append(
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
@ -86,7 +86,7 @@ var subCmdGarageCLI = subCmd{
|
|||||||
env := subCmdCtx.env
|
env := subCmdCtx.env
|
||||||
|
|
||||||
var (
|
var (
|
||||||
binPath = env.BinPath("garage")
|
binPath = "garage"
|
||||||
args = append([]string{"garage"}, subCmdCtx.args...)
|
args = append([]string{"garage"}, subCmdCtx.args...)
|
||||||
cliEnv = append(
|
cliEnv = append(
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
|
"cryptic-net/daemon"
|
||||||
"cryptic-net/garage"
|
"cryptic-net/garage"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -13,9 +14,28 @@ import (
|
|||||||
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func waitForGarageAndNebula(ctx context.Context, env crypticnet.Env) error {
|
// newGarageAdminClient will return an AdminClient for a local garage instance,
|
||||||
|
// or it will _panic_ if there is no local instance configured.
|
||||||
|
func newGarageAdminClient(
|
||||||
|
env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
|
) *garage.AdminClient {
|
||||||
|
|
||||||
allocs := env.ThisDaemon().Storage.Allocations
|
thisHost := env.Bootstrap.ThisHost()
|
||||||
|
|
||||||
|
return garage.NewAdminClient(
|
||||||
|
net.JoinHostPort(
|
||||||
|
thisHost.Nebula.IP,
|
||||||
|
strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort),
|
||||||
|
),
|
||||||
|
env.Bootstrap.GarageAdminToken,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForGarageAndNebula(
|
||||||
|
ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
allocs := daemonConfig.Storage.Allocations
|
||||||
|
|
||||||
// if this host doesn't have any allocations specified then fall back to
|
// if this host doesn't have any allocations specified then fall back to
|
||||||
// waiting for nebula
|
// waiting for nebula
|
||||||
@ -44,9 +64,9 @@ func waitForGarageAndNebula(ctx context.Context, env crypticnet.Env) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func garageWriteChildConf(
|
func garageWriteChildConfig(
|
||||||
env crypticnet.Env,
|
env crypticnet.Env,
|
||||||
alloc crypticnet.DaemonYmlStorageAllocation,
|
alloc daemon.ConfigStorageAllocation,
|
||||||
) (
|
) (
|
||||||
string, error,
|
string, error,
|
||||||
) {
|
) {
|
||||||
@ -100,13 +120,17 @@ func garageWriteChildConf(
|
|||||||
return garageTomlPath, nil
|
return garageTomlPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func garagePmuxProcConfigs(env crypticnet.Env) ([]pmuxlib.ProcessConfig, error) {
|
func garagePmuxProcConfigs(
|
||||||
|
env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
|
) (
|
||||||
|
[]pmuxlib.ProcessConfig, error,
|
||||||
|
) {
|
||||||
|
|
||||||
var pmuxProcConfigs []pmuxlib.ProcessConfig
|
var pmuxProcConfigs []pmuxlib.ProcessConfig
|
||||||
|
|
||||||
for _, alloc := range env.ThisDaemon().Storage.Allocations {
|
for _, alloc := range daemonConfig.Storage.Allocations {
|
||||||
|
|
||||||
childConfPath, err := garageWriteChildConf(env, alloc)
|
childConfigPath, err := garageWriteChildConfig(env, alloc)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
||||||
@ -115,7 +139,7 @@ func garagePmuxProcConfigs(env crypticnet.Env) ([]pmuxlib.ProcessConfig, error)
|
|||||||
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
||||||
Name: fmt.Sprintf("garage-%d", alloc.RPCPort),
|
Name: fmt.Sprintf("garage-%d", alloc.RPCPort),
|
||||||
Cmd: "garage",
|
Cmd: "garage",
|
||||||
Args: []string{"-c", childConfPath, "server"},
|
Args: []string{"-c", childConfigPath, "server"},
|
||||||
StartAfterFunc: func(ctx context.Context) error {
|
StartAfterFunc: func(ctx context.Context) error {
|
||||||
return waitForNebula(ctx, env)
|
return waitForNebula(ctx, env)
|
||||||
},
|
},
|
||||||
@ -125,10 +149,12 @@ func garagePmuxProcConfigs(env crypticnet.Env) ([]pmuxlib.ProcessConfig, error)
|
|||||||
return pmuxProcConfigs, nil
|
return pmuxProcConfigs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func garageInitializeGlobalBucket(ctx context.Context, env crypticnet.Env) error {
|
func garageInitializeGlobalBucket(
|
||||||
|
ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
|
) error {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
adminClient = env.GarageAdminClient()
|
adminClient = newGarageAdminClient(env, daemonConfig)
|
||||||
globalBucketCreds = env.Bootstrap.GarageGlobalBucketS3APICredentials
|
globalBucketCreds = env.Bootstrap.GarageGlobalBucketS3APICredentials
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -183,14 +209,16 @@ func garageInitializeGlobalBucket(ctx context.Context, env crypticnet.Env) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func garageApplyLayout(ctx context.Context, env crypticnet.Env) error {
|
func garageApplyLayout(
|
||||||
|
ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
|
) error {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
adminClient = env.GarageAdminClient()
|
adminClient = newGarageAdminClient(env, daemonConfig)
|
||||||
thisHost = env.Bootstrap.ThisHost()
|
thisHost = env.Bootstrap.ThisHost()
|
||||||
hostName = thisHost.Name
|
hostName = thisHost.Name
|
||||||
ip = thisHost.Nebula.IP
|
ip = thisHost.Nebula.IP
|
||||||
allocs = env.ThisDaemon().Storage.Allocations
|
allocs = daemonConfig.Storage.Allocations
|
||||||
)
|
)
|
||||||
|
|
||||||
type peerLayout struct {
|
type peerLayout struct {
|
||||||
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
|
"cryptic-net/daemon"
|
||||||
"cryptic-net/yamlutil"
|
"cryptic-net/yamlutil"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -33,9 +34,11 @@ func waitForNebula(ctx context.Context, env crypticnet.Env) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func nebulaPmuxProcConfig(env crypticnet.Env) (pmuxlib.ProcessConfig, error) {
|
func nebulaPmuxProcConfig(
|
||||||
|
env crypticnet.Env, daemonConfig daemon.Config,
|
||||||
thisDaemon := env.ThisDaemon()
|
) (
|
||||||
|
pmuxlib.ProcessConfig, error,
|
||||||
|
) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lighthouseHostIPs []string
|
lighthouseHostIPs []string
|
||||||
@ -66,10 +69,10 @@ func nebulaPmuxProcConfig(env crypticnet.Env) (pmuxlib.ProcessConfig, error) {
|
|||||||
"tun": map[string]interface{}{
|
"tun": map[string]interface{}{
|
||||||
"dev": "cryptic-net-nebula",
|
"dev": "cryptic-net-nebula",
|
||||||
},
|
},
|
||||||
"firewall": thisDaemon.VPN.Firewall,
|
"firewall": daemonConfig.VPN.Firewall,
|
||||||
}
|
}
|
||||||
|
|
||||||
if publicAddr := env.ThisDaemon().VPN.PublicAddr; publicAddr == "" {
|
if publicAddr := daemonConfig.VPN.PublicAddr; publicAddr == "" {
|
||||||
|
|
||||||
config["listen"] = map[string]string{
|
config["listen"] = map[string]string{
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package crypticnet
|
package daemon
|
||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
@ -27,9 +27,9 @@ type ConfigFirewallRule struct {
|
|||||||
CAName string `yaml:"ca_name,omitempty"`
|
CAName string `yaml:"ca_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DaemonYmlStorageAllocation describes the structure of each storage allocation
|
// ConfigStorageAllocation describes the structure of each storage allocation
|
||||||
// within the daemon.yml file.
|
// within the daemon config file.
|
||||||
type DaemonYmlStorageAllocation struct {
|
type ConfigStorageAllocation struct {
|
||||||
DataPath string `yaml:"data_path"`
|
DataPath string `yaml:"data_path"`
|
||||||
MetaPath string `yaml:"meta_path"`
|
MetaPath string `yaml:"meta_path"`
|
||||||
Capacity int `yaml:"capacity"`
|
Capacity int `yaml:"capacity"`
|
||||||
@ -38,8 +38,8 @@ type DaemonYmlStorageAllocation struct {
|
|||||||
AdminPort int `yaml:"admin_port"`
|
AdminPort int `yaml:"admin_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DaemonYml describes the structure of the daemon.yml file.
|
// Config describes the structure of the daemon config file.
|
||||||
type DaemonYml struct {
|
type Config struct {
|
||||||
DNS struct {
|
DNS struct {
|
||||||
Resolvers []string `yaml:"resolvers"`
|
Resolvers []string `yaml:"resolvers"`
|
||||||
} `yaml:"dns"`
|
} `yaml:"dns"`
|
||||||
@ -48,30 +48,29 @@ type DaemonYml struct {
|
|||||||
Firewall ConfigFirewall `yaml:"firewall"`
|
Firewall ConfigFirewall `yaml:"firewall"`
|
||||||
} `yaml:"vpn"`
|
} `yaml:"vpn"`
|
||||||
Storage struct {
|
Storage struct {
|
||||||
Allocations []DaemonYmlStorageAllocation
|
Allocations []ConfigStorageAllocation
|
||||||
} `yaml:"storage"`
|
} `yaml:"storage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillDefaults fills in default values in the DaemonYml.
|
func (c *Config) fillDefaults() {
|
||||||
func (d *DaemonYml) FillDefaults() {
|
|
||||||
|
|
||||||
var firewallGarageInbound []ConfigFirewallRule
|
var firewallGarageInbound []ConfigFirewallRule
|
||||||
|
|
||||||
for i := range d.Storage.Allocations {
|
for i := range c.Storage.Allocations {
|
||||||
|
|
||||||
if d.Storage.Allocations[i].RPCPort == 0 {
|
if c.Storage.Allocations[i].RPCPort == 0 {
|
||||||
d.Storage.Allocations[i].RPCPort = 3900 + (i * 10)
|
c.Storage.Allocations[i].RPCPort = 3900 + (i * 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Storage.Allocations[i].S3APIPort == 0 {
|
if c.Storage.Allocations[i].S3APIPort == 0 {
|
||||||
d.Storage.Allocations[i].S3APIPort = 3901 + (i * 10)
|
c.Storage.Allocations[i].S3APIPort = 3901 + (i * 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Storage.Allocations[i].AdminPort == 0 {
|
if c.Storage.Allocations[i].AdminPort == 0 {
|
||||||
d.Storage.Allocations[i].AdminPort = 3902 + (i * 10)
|
c.Storage.Allocations[i].AdminPort = 3902 + (i * 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc := d.Storage.Allocations[i]
|
alloc := c.Storage.Allocations[i]
|
||||||
|
|
||||||
firewallGarageInbound = append(
|
firewallGarageInbound = append(
|
||||||
firewallGarageInbound,
|
firewallGarageInbound,
|
||||||
@ -88,8 +87,8 @@ func (d *DaemonYml) FillDefaults() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.VPN.Firewall.Inbound = append(
|
c.VPN.Firewall.Inbound = append(
|
||||||
d.VPN.Firewall.Inbound,
|
c.VPN.Firewall.Inbound,
|
||||||
firewallGarageInbound...,
|
firewallGarageInbound...,
|
||||||
)
|
)
|
||||||
}
|
}
|
85
entrypoint/src/daemon/daemon.go
Normal file
85
entrypoint/src/daemon/daemon.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Package daemon contains types and functions related specifically to the
|
||||||
|
// cryptic-net daemon.
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cryptic-net/yamlutil"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultConfigPath(appDirPath string) string {
|
||||||
|
return filepath.Join(appDirPath, "etc", "daemon.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDefaultConfig copies the daemon config file embedded in the AppDir into
|
||||||
|
// the given io.Writer.
|
||||||
|
func CopyDefaultConfig(into io.Writer, appDirPath string) error {
|
||||||
|
|
||||||
|
defaultConfigPath := defaultConfigPath(appDirPath)
|
||||||
|
|
||||||
|
f, err := os.Open(defaultConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening daemon config at %q: %w", defaultConfigPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(into, f); err != nil {
|
||||||
|
return fmt.Errorf("copying daemon config from %q: %w", defaultConfigPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads the daemon config from userConfigPath, merges it with
|
||||||
|
// the default found in the appDirPath, and returns the result.
|
||||||
|
//
|
||||||
|
// If userConfigPath is not given then the default is loaded and returned.
|
||||||
|
func LoadConfig(
|
||||||
|
appDirPath, userConfigPath string,
|
||||||
|
) (
|
||||||
|
Config, error,
|
||||||
|
) {
|
||||||
|
|
||||||
|
defaultConfigPath := defaultConfigPath(appDirPath)
|
||||||
|
|
||||||
|
var fullDaemon map[string]interface{}
|
||||||
|
|
||||||
|
if err := yamlutil.LoadYamlFile(&fullDaemon, defaultConfigPath); err != nil {
|
||||||
|
return Config{}, fmt.Errorf("parsing default daemon config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userConfigPath != "" {
|
||||||
|
|
||||||
|
var daemonConfig map[string]interface{}
|
||||||
|
if err := yamlutil.LoadYamlFile(&daemonConfig, userConfigPath); err != nil {
|
||||||
|
return Config{}, fmt.Errorf("parsing %q: %w", userConfigPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mergo.Merge(&fullDaemon, daemonConfig, mergo.WithOverride)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("merging contents of file %q: %w", userConfigPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fullDaemonB, err := yaml.Marshal(fullDaemon)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("yaml marshaling: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
if err := yaml.Unmarshal(fullDaemonB, &config); err != nil {
|
||||||
|
return Config{}, fmt.Errorf("yaml unmarshaling back into Config struct: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.fillDefaults()
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
@ -3,17 +3,12 @@ package crypticnet
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
"cryptic-net/garage"
|
|
||||||
"cryptic-net/yamlutil"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/adrg/xdg"
|
"github.com/adrg/xdg"
|
||||||
@ -25,7 +20,6 @@ type Env struct {
|
|||||||
Context context.Context
|
Context context.Context
|
||||||
|
|
||||||
AppDirPath string
|
AppDirPath string
|
||||||
DaemonYmlPath string
|
|
||||||
RuntimeDirPath string
|
RuntimeDirPath string
|
||||||
DataDirPath string
|
DataDirPath string
|
||||||
|
|
||||||
@ -33,9 +27,6 @@ type Env struct {
|
|||||||
// found, then these fields will not be set.
|
// found, then these fields will not be set.
|
||||||
BootstrapPath string
|
BootstrapPath string
|
||||||
Bootstrap bootstrap.Bootstrap
|
Bootstrap bootstrap.Bootstrap
|
||||||
|
|
||||||
thisDaemon DaemonYml
|
|
||||||
thisDaemonOnce sync.Once
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAppDirPath() string {
|
func getAppDirPath() string {
|
||||||
@ -58,7 +49,6 @@ func NewEnv(bootstrapOptional bool) (Env, error) {
|
|||||||
|
|
||||||
env := Env{
|
env := Env{
|
||||||
AppDirPath: appDirPath,
|
AppDirPath: appDirPath,
|
||||||
DaemonYmlPath: filepath.Join(runtimeDirPath, "daemon.yml"),
|
|
||||||
RuntimeDirPath: runtimeDirPath,
|
RuntimeDirPath: runtimeDirPath,
|
||||||
DataDirPath: filepath.Join(xdg.DataHome, "cryptic-net"),
|
DataDirPath: filepath.Join(xdg.DataHome, "cryptic-net"),
|
||||||
}
|
}
|
||||||
@ -154,37 +144,3 @@ func (e Env) init(bootstrapOptional bool) (Env, error) {
|
|||||||
|
|
||||||
return e.initBootstrap(bootstrapOptional)
|
return e.initBootstrap(bootstrapOptional)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThisDaemon returns the DaemonYml (loaded from DaemonYmlPath) for the
|
|
||||||
// currently running process.
|
|
||||||
func (e Env) ThisDaemon() DaemonYml {
|
|
||||||
e.thisDaemonOnce.Do(func() {
|
|
||||||
if err := yamlutil.LoadYamlFile(&e.thisDaemon, e.DaemonYmlPath); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
e.thisDaemon.FillDefaults()
|
|
||||||
})
|
|
||||||
return e.thisDaemon
|
|
||||||
}
|
|
||||||
|
|
||||||
// BinPath returns the absolute path to a binary in the AppDir.
|
|
||||||
func (e Env) BinPath(name string) string {
|
|
||||||
return filepath.Join(e.AppDirPath, "bin", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GarageAdminClient will return an AdminClient for a local garage instance, or
|
|
||||||
// it will _panic_ if there is no local instance configured.
|
|
||||||
func (e Env) GarageAdminClient() *garage.AdminClient {
|
|
||||||
|
|
||||||
thisHost := e.Bootstrap.ThisHost()
|
|
||||||
thisDaemon := e.ThisDaemon()
|
|
||||||
|
|
||||||
return garage.NewAdminClient(
|
|
||||||
net.JoinHostPort(
|
|
||||||
thisHost.Nebula.IP,
|
|
||||||
strconv.Itoa(thisDaemon.Storage.Allocations[0].AdminPort),
|
|
||||||
),
|
|
||||||
e.Bootstrap.GarageAdminToken,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user