Compare commits
2 Commits
3b19552173
...
cf00561206
Author | SHA1 | Date | |
---|---|---|---|
|
cf00561206 | ||
|
18422a1084 |
@ -17,6 +17,7 @@ state AppDir {
|
|||||||
entrypoint : * Merge given and default daemon.yml files
|
entrypoint : * Merge given and default daemon.yml files
|
||||||
entrypoint : * Copy bootstrap.tgz into $_DATA_DIR_PATH, if it's not there
|
entrypoint : * Copy bootstrap.tgz into $_DATA_DIR_PATH, if it's not there
|
||||||
entrypoint : * Merge daemon.yml config into bootstrap.tgz
|
entrypoint : * Merge daemon.yml config into bootstrap.tgz
|
||||||
|
entrypoint : * Create $_RUNTIME_DIR_PATH/garage-N.toml\n (one per storage allocation)
|
||||||
entrypoint : * Run child processes
|
entrypoint : * Run child processes
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,20 +42,17 @@ state AppDir {
|
|||||||
entrypoint --> nebulaEntrypoint : child
|
entrypoint --> nebulaEntrypoint : child
|
||||||
nebulaEntrypoint --> nebula : exec
|
nebulaEntrypoint --> nebula : exec
|
||||||
|
|
||||||
state "./bin/cryptic-net-main garage-entrypoint" as garageEntrypoint {
|
state "Garage processes (only if any storage allocs are defined)" as garageChildren {
|
||||||
garageEntrypoint : * Create $_RUNTIME_DIR_PATH/garage-N.toml\n (one per storage allocation)
|
|
||||||
garageEntrypoint : * Run child processes
|
state "./bin/garage -c $_RUNTIME_DIR_PATH/garage-N.toml server" as garage
|
||||||
|
state "./bin/garage-apply-layout-diff" as garageApplyLayoutDiff {
|
||||||
|
garageApplyLayoutDiff : * Runs once then exits
|
||||||
|
garageApplyLayoutDiff : * Updates cluster topo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state "./bin/garage -c $_RUNTIME_DIR_PATH/garage-N.toml server" as garage
|
entrypoint --> garage : child (one per storage allocation)
|
||||||
state "./bin/garage-apply-layout-diff" as garageApplyLayoutDiff {
|
entrypoint --> garageApplyLayoutDiff : child
|
||||||
garageApplyLayoutDiff : * Runs once then exits
|
|
||||||
garageApplyLayoutDiff : * Updates cluster topo
|
|
||||||
}
|
|
||||||
|
|
||||||
entrypoint --> garageEntrypoint : child (only if >1 storage allocation defined in daemon.yml)
|
|
||||||
garageEntrypoint --> garage : child (one per storage allocation)
|
|
||||||
garageEntrypoint --> garageApplyLayoutDiff : child
|
|
||||||
|
|
||||||
state "./bin/cryptic-net-main update-global-bucket" as updateGlobalBucket {
|
state "./bin/cryptic-net-main update-global-bucket" as updateGlobalBucket {
|
||||||
updateGlobalBucket : * Runs once then exits
|
updateGlobalBucket : * Runs once then exits
|
||||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
@ -16,7 +16,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cryptic-net/cmd/entrypoint"
|
"cryptic-net/cmd/entrypoint"
|
||||||
garage_entrypoint "cryptic-net/cmd/garage-entrypoint"
|
|
||||||
garage_layout_diff "cryptic-net/cmd/garage-layout-diff"
|
garage_layout_diff "cryptic-net/cmd/garage-layout-diff"
|
||||||
garage_peer_keygen "cryptic-net/cmd/garage-peer-keygen"
|
garage_peer_keygen "cryptic-net/cmd/garage-peer-keygen"
|
||||||
nebula_entrypoint "cryptic-net/cmd/nebula-entrypoint"
|
nebula_entrypoint "cryptic-net/cmd/nebula-entrypoint"
|
||||||
@ -32,7 +31,6 @@ type mainFn struct {
|
|||||||
|
|
||||||
var mainFns = []mainFn{
|
var mainFns = []mainFn{
|
||||||
{"entrypoint", entrypoint.Main},
|
{"entrypoint", entrypoint.Main},
|
||||||
{"garage-entrypoint", garage_entrypoint.Main},
|
|
||||||
{"garage-layout-diff", garage_layout_diff.Main},
|
{"garage-layout-diff", garage_layout_diff.Main},
|
||||||
{"garage-peer-keygen", garage_peer_keygen.Main},
|
{"garage-peer-keygen", garage_peer_keygen.Main},
|
||||||
{"nebula-entrypoint", nebula_entrypoint.Main},
|
{"nebula-entrypoint", nebula_entrypoint.Main},
|
||||||
|
112
go-workspace/src/cmd/entrypoint/admin.go
Normal file
112
go-workspace/src/cmd/entrypoint/admin.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package entrypoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cryptic-net/admin"
|
||||||
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/nebula"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readAdmin(path string) (admin.Admin, error) {
|
||||||
|
|
||||||
|
if path == "-" {
|
||||||
|
|
||||||
|
adm, err := admin.FromReader(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return admin.Admin{}, fmt.Errorf("parsing admin.tgz from stdin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return admin.Admin{}, fmt.Errorf("opening file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return admin.FromReader(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
var subCmdAdminMakeBootstrap = subCmd{
|
||||||
|
name: "make-bootstrap",
|
||||||
|
descr: "Creates a new bootstrap.tgz file for a particular host and writes it to stdout",
|
||||||
|
checkLock: true,
|
||||||
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
|
||||||
|
flags := subCmdCtx.flagSet(false)
|
||||||
|
|
||||||
|
name := flags.StringP(
|
||||||
|
"name", "n", "",
|
||||||
|
"Name of the host to generate bootstrap.tgz for",
|
||||||
|
)
|
||||||
|
|
||||||
|
adminPath := flags.StringP(
|
||||||
|
"admin-path", "a", "",
|
||||||
|
`Path to admin.tgz file. If the given path is "-" then stdin is used.`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *name == "" || *adminPath == "" {
|
||||||
|
return errors.New("--name and --admin-path are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
env := subCmdCtx.env
|
||||||
|
|
||||||
|
adm, err := readAdmin(*adminPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := env.Bootstrap.GlobalBucketS3APIClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating client for global bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE this isn't _technically_ required, but if the `hosts add`
|
||||||
|
// command for this host has been run recently then it might not have
|
||||||
|
// made it into the bootstrap file yet, and so won't be in
|
||||||
|
// `env.Bootstrap`.
|
||||||
|
hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("retrieving host info from garage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, ok := hosts[*name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("couldn't find host into for %q in garage, has `cryptic-net hosts add` been run yet?", *name)
|
||||||
|
}
|
||||||
|
|
||||||
|
nebulaHostCert, err := nebula.NewHostCert(adm.NebulaCACert, host.Name, host.Nebula.IP)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBootstrap := bootstrap.Bootstrap{
|
||||||
|
Hosts: hosts,
|
||||||
|
HostName: *name,
|
||||||
|
|
||||||
|
NebulaHostCert: nebulaHostCert,
|
||||||
|
|
||||||
|
GarageRPCSecret: adm.GarageRPCSecret,
|
||||||
|
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBootstrap.WriteTo(os.Stdout)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var subCmdAdmin = subCmd{
|
||||||
|
name: "admin",
|
||||||
|
descr: "Sub-commands which only admins can run",
|
||||||
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
return subCmdCtx.doSubCmd(
|
||||||
|
subCmdAdminMakeBootstrap,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
@ -5,23 +5,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
"cryptic-net/garage"
|
"cryptic-net/garage"
|
||||||
"cryptic-net/yamlutil"
|
|
||||||
|
|
||||||
"github.com/cryptic-io/pmux/pmuxlib"
|
"github.com/cryptic-io/pmux/pmuxlib"
|
||||||
"github.com/imdario/mergo"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The daemon sub-command deals with starting an actual cryptic-net daemon
|
// The daemon sub-command deals with starting an actual cryptic-net daemon
|
||||||
@ -46,70 +39,6 @@ import (
|
|||||||
//
|
//
|
||||||
// * (On exit) cleans up the runtime directory.
|
// * (On exit) cleans up the runtime directory.
|
||||||
|
|
||||||
func writeDaemonYml(userDaemonYmlPath, builtinDaemonYmlPath, runtimeDirPath string) error {
|
|
||||||
|
|
||||||
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(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
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyBootstrapToDataDir(env *crypticnet.Env, r io.Reader) error {
|
|
||||||
|
|
||||||
path := env.DataDirBootstrapPath()
|
|
||||||
dirPath := filepath.Dir(path)
|
|
||||||
|
|
||||||
if err := os.MkdirAll(dirPath, 0700); err != nil {
|
|
||||||
return fmt.Errorf("creating directory %q: %w", dirPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating file %q: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(f, r)
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("copying bootstrap file to %q: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := env.LoadBootstrap(path); err != nil {
|
|
||||||
return fmt.Errorf("loading bootstrap from %q: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates a new bootstrap file using available information from the network. If
|
// creates a new bootstrap file using available information from the network. If
|
||||||
// the new bootstrap file is different than the existing one, the existing one
|
// the new bootstrap file is different than the existing one, the existing one
|
||||||
// is overwritten, ReloadBootstrap is called on env, true is returned.
|
// is overwritten, ReloadBootstrap is called on env, true is returned.
|
||||||
@ -156,65 +85,31 @@ func runDaemonPmuxOnce(env *crypticnet.Env, s3Client garage.S3APIClient) error {
|
|||||||
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)
|
||||||
|
|
||||||
pmuxProcConfigs := []pmuxlib.ProcessConfig{
|
pmuxProcConfigs := []pmuxlib.ProcessConfig{
|
||||||
{
|
nebulaEntrypointPmuxProcConfig(),
|
||||||
Name: "nebula",
|
|
||||||
Cmd: "cryptic-net-main",
|
|
||||||
Args: []string{
|
|
||||||
"nebula-entrypoint",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "dnsmasq",
|
Name: "dnsmasq",
|
||||||
Cmd: "bash",
|
Cmd: "bash",
|
||||||
Args: []string{
|
Args: waitForNebulaArgs(env, "dnsmasq-entrypoint"),
|
||||||
"wait-for-ip",
|
|
||||||
thisHost.Nebula.IP,
|
|
||||||
"dnsmasq-entrypoint",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if len(thisDaemon.Storage.Allocations) > 0 {
|
||||||
var args []string
|
|
||||||
|
|
||||||
if allocs := thisDaemon.Storage.Allocations; len(allocs) > 0 {
|
garageChildrenPmuxProcConfigs, err := garageChildrenPmuxProcConfigs(env)
|
||||||
for _, alloc := range allocs {
|
if err != nil {
|
||||||
args = append(
|
return fmt.Errorf("generating garage children configs: %w", err)
|
||||||
args,
|
|
||||||
"wait-for",
|
|
||||||
net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
|
|
||||||
"--",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args = []string{
|
|
||||||
"wait-for-ip",
|
|
||||||
thisHost.Nebula.IP,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
pmuxProcConfigs = append(pmuxProcConfigs, garageChildrenPmuxProcConfigs...)
|
||||||
Name: "update-global-bucket",
|
pmuxProcConfigs = append(pmuxProcConfigs, garageApplyLayoutDiffPmuxProcConfig(env))
|
||||||
Cmd: "bash",
|
|
||||||
Args: append(args, "update-global-bucket"),
|
|
||||||
NoRestartOn: []int{0},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(thisDaemon.Storage.Allocations) > 0 {
|
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
||||||
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
Name: "update-global-bucket",
|
||||||
Name: "garage",
|
Cmd: "bash",
|
||||||
Cmd: "bash",
|
Args: waitForGarageArgs(env, "update-global-bucket"),
|
||||||
Args: []string{
|
NoRestartOn: []int{0},
|
||||||
"wait-for-ip",
|
})
|
||||||
thisHost.Nebula.IP,
|
|
||||||
"cryptic-net-main", "garage-entrypoint",
|
|
||||||
},
|
|
||||||
|
|
||||||
// garage can take a while to clean up
|
|
||||||
SigKillWait: (1 * time.Minute) + (10 * time.Second),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pmuxConfig := pmuxlib.Config{Processes: pmuxProcConfigs}
|
pmuxConfig := pmuxlib.Config{Processes: pmuxProcConfigs}
|
||||||
|
|
||||||
@ -360,48 +255,17 @@ var subCmdDaemon = subCmd{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeDaemonYml(*daemonYmlPath, builtinDaemonYmlPath, runtimeDirPath); err != nil {
|
if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil {
|
||||||
return fmt.Errorf("generating daemon.yml file: %w", err)
|
return fmt.Errorf("generating daemon.yml file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// 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 daemon.yml. 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 updated in garage using update-global-bucket, so other hosts
|
||||||
// get updated in garage using update-global-bucket, so other hosts
|
// will see it as well.
|
||||||
// will see it as well.
|
if err := mergeDaemonIntoBootstrap(env); err != nil {
|
||||||
|
return fmt.Errorf("merging daemon.yml into bootstrap data: %w", err)
|
||||||
// ThisDaemon can only be called after writeDaemonYml.
|
|
||||||
daemon := env.ThisDaemon()
|
|
||||||
host := env.Bootstrap.ThisHost()
|
|
||||||
|
|
||||||
host.Nebula.PublicAddr = daemon.VPN.PublicAddr
|
|
||||||
|
|
||||||
host.Garage = nil
|
|
||||||
|
|
||||||
if allocs := daemon.Storage.Allocations; len(allocs) > 0 {
|
|
||||||
|
|
||||||
host.Garage = new(bootstrap.GarageHost)
|
|
||||||
|
|
||||||
for _, alloc := range allocs {
|
|
||||||
host.Garage.Instances = append(host.Garage.Instances, bootstrap.GarageHostInstance{
|
|
||||||
RPCPort: alloc.RPCPort,
|
|
||||||
S3APIPort: alloc.S3APIPort,
|
|
||||||
WebPort: alloc.WebPort,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
env.Bootstrap.Hosts[host.Name] = host
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if err := env.Bootstrap.WithHosts(env.Bootstrap.Hosts).WriteTo(buf); err != nil {
|
|
||||||
return fmt.Errorf("writing new bootstrap file to buffer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := copyBootstrapToDataDir(env, buf); err != nil {
|
|
||||||
return fmt.Errorf("copying new bootstrap file to data dir: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, val := range env.ToMap() {
|
for key, val := range env.ToMap() {
|
||||||
|
205
go-workspace/src/cmd/entrypoint/daemon_util.go
Normal file
205
go-workspace/src/cmd/entrypoint/daemon_util.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package entrypoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
crypticnet "cryptic-net"
|
||||||
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/garage"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cryptic-io/pmux/pmuxlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyBootstrapToDataDir(env *crypticnet.Env, r io.Reader) error {
|
||||||
|
|
||||||
|
path := env.DataDirBootstrapPath()
|
||||||
|
dirPath := filepath.Dir(path)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dirPath, 0700); err != nil {
|
||||||
|
return fmt.Errorf("creating directory %q: %w", dirPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating file %q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(f, r)
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copying bootstrap file to %q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := env.LoadBootstrap(path); err != nil {
|
||||||
|
return fmt.Errorf("loading bootstrap from %q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeDaemonIntoBootstrap(env *crypticnet.Env) error {
|
||||||
|
daemon := env.ThisDaemon()
|
||||||
|
host := env.Bootstrap.ThisHost()
|
||||||
|
|
||||||
|
host.Nebula.PublicAddr = daemon.VPN.PublicAddr
|
||||||
|
|
||||||
|
host.Garage = nil
|
||||||
|
|
||||||
|
if allocs := daemon.Storage.Allocations; len(allocs) > 0 {
|
||||||
|
|
||||||
|
host.Garage = new(bootstrap.GarageHost)
|
||||||
|
|
||||||
|
for _, alloc := range allocs {
|
||||||
|
host.Garage.Instances = append(host.Garage.Instances, bootstrap.GarageHostInstance{
|
||||||
|
RPCPort: alloc.RPCPort,
|
||||||
|
S3APIPort: alloc.S3APIPort,
|
||||||
|
WebPort: alloc.WebPort,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env.Bootstrap.Hosts[host.Name] = host
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := env.Bootstrap.WithHosts(env.Bootstrap.Hosts).WriteTo(buf); err != nil {
|
||||||
|
return fmt.Errorf("writing new bootstrap file to buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyBootstrapToDataDir(env, buf); err != nil {
|
||||||
|
return fmt.Errorf("copying new bootstrap file to data dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForNebulaArgs(env *crypticnet.Env, args ...string) []string {
|
||||||
|
thisHost := env.Bootstrap.ThisHost()
|
||||||
|
return append([]string{"wait-for-ip", thisHost.Nebula.IP}, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForGarageArgs(env *crypticnet.Env, args ...string) []string {
|
||||||
|
|
||||||
|
thisHost := env.Bootstrap.ThisHost()
|
||||||
|
allocs := env.ThisDaemon().Storage.Allocations
|
||||||
|
|
||||||
|
if len(allocs) == 0 {
|
||||||
|
return waitForNebulaArgs(env, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var preArgs []string
|
||||||
|
|
||||||
|
for _, alloc := range allocs {
|
||||||
|
preArgs = append(
|
||||||
|
preArgs,
|
||||||
|
"wait-for",
|
||||||
|
net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
|
||||||
|
"--",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(preArgs, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nebulaEntrypointPmuxProcConfig() pmuxlib.ProcessConfig {
|
||||||
|
return pmuxlib.ProcessConfig{
|
||||||
|
Name: "nebula",
|
||||||
|
Cmd: "cryptic-net-main",
|
||||||
|
Args: []string{
|
||||||
|
"nebula-entrypoint",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func garageWriteChildConf(
|
||||||
|
env *crypticnet.Env,
|
||||||
|
alloc crypticnet.DaemonYmlStorageAllocation,
|
||||||
|
) (
|
||||||
|
string, error,
|
||||||
|
) {
|
||||||
|
|
||||||
|
if err := os.MkdirAll(alloc.MetaPath, 0750); err != nil {
|
||||||
|
return "", fmt.Errorf("making directory %q: %w", alloc.MetaPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
thisHost := env.Bootstrap.ThisHost()
|
||||||
|
|
||||||
|
peer := garage.Peer{
|
||||||
|
IP: thisHost.Nebula.IP,
|
||||||
|
RPCPort: alloc.RPCPort,
|
||||||
|
S3APIPort: alloc.S3APIPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, privKey := peer.RPCPeerKey()
|
||||||
|
|
||||||
|
nodeKeyPath := filepath.Join(alloc.MetaPath, "node_key")
|
||||||
|
nodeKeyPubPath := filepath.Join(alloc.MetaPath, "node_keypub")
|
||||||
|
|
||||||
|
if err := os.WriteFile(nodeKeyPath, privKey, 0400); err != nil {
|
||||||
|
return "", fmt.Errorf("writing private key to %q: %w", nodeKeyPath, err)
|
||||||
|
|
||||||
|
} else if err := os.WriteFile(nodeKeyPubPath, pubKey, 0440); err != nil {
|
||||||
|
return "", fmt.Errorf("writing public key to %q: %w", nodeKeyPubPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
garageTomlPath := filepath.Join(
|
||||||
|
env.RuntimeDirPath, fmt.Sprintf("garage-%d.toml", alloc.RPCPort),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := garage.WriteGarageTomlFile(garageTomlPath, garage.GarageTomlData{
|
||||||
|
MetaPath: alloc.MetaPath,
|
||||||
|
DataPath: alloc.DataPath,
|
||||||
|
|
||||||
|
RPCSecret: env.Bootstrap.GarageRPCSecret,
|
||||||
|
|
||||||
|
RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
|
||||||
|
APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)),
|
||||||
|
WebAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.WebPort)),
|
||||||
|
|
||||||
|
BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("creating garage.toml file at %q: %w", garageTomlPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return garageTomlPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func garageChildrenPmuxProcConfigs(env *crypticnet.Env) ([]pmuxlib.ProcessConfig, error) {
|
||||||
|
|
||||||
|
var pmuxProcConfigs []pmuxlib.ProcessConfig
|
||||||
|
|
||||||
|
for _, alloc := range env.ThisDaemon().Storage.Allocations {
|
||||||
|
|
||||||
|
childConfPath, err := garageWriteChildConf(env, alloc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
||||||
|
Name: fmt.Sprintf("garage-%d", alloc.RPCPort),
|
||||||
|
Cmd: "garage",
|
||||||
|
Args: []string{"-c", childConfPath, "server"},
|
||||||
|
SigKillWait: 1 * time.Minute,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return pmuxProcConfigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func garageApplyLayoutDiffPmuxProcConfig(env *crypticnet.Env) pmuxlib.ProcessConfig {
|
||||||
|
return pmuxlib.ProcessConfig{
|
||||||
|
Name: "garage-apply-layout-diff",
|
||||||
|
Cmd: "bash",
|
||||||
|
Args: waitForGarageArgs(env, "bash", "garage-apply-layout-diff"),
|
||||||
|
NoRestartOn: []int{0},
|
||||||
|
}
|
||||||
|
}
|
72
go-workspace/src/cmd/entrypoint/daemon_yml.go
Normal file
72
go-workspace/src/cmd/entrypoint/daemon_yml.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package entrypoint
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
package entrypoint
|
package entrypoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cryptic-net/admin"
|
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
"cryptic-net/nebula"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -140,107 +138,14 @@ var subCmdHostsDelete = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func readAdmin(path string) (admin.Admin, error) {
|
|
||||||
|
|
||||||
if path == "-" {
|
|
||||||
|
|
||||||
adm, err := admin.FromReader(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
return admin.Admin{}, fmt.Errorf("parsing admin.tgz from stdin: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return adm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return admin.Admin{}, fmt.Errorf("opening file: %w", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return admin.FromReader(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
var subCmdHostsMakeBootstrap = subCmd{
|
|
||||||
name: "make-bootstrap",
|
|
||||||
descr: "Creates a new bootstrap.tgz file for a particular host and writes it to stdout",
|
|
||||||
checkLock: true,
|
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
|
||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
|
||||||
|
|
||||||
name := flags.StringP(
|
|
||||||
"name", "n", "",
|
|
||||||
"Name of the host to generate bootstrap.tgz for",
|
|
||||||
)
|
|
||||||
|
|
||||||
adminPath := flags.StringP(
|
|
||||||
"admin-path", "a", "",
|
|
||||||
`Path to admin.tgz file. If the given path is "-" then stdin is used.`,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := flags.Parse(subCmdCtx.args); err != nil {
|
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *name == "" || *adminPath == "" {
|
|
||||||
return errors.New("--name and --admin-path are required")
|
|
||||||
}
|
|
||||||
|
|
||||||
env := subCmdCtx.env
|
|
||||||
|
|
||||||
adm, err := readAdmin(*adminPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := env.Bootstrap.GlobalBucketS3APIClient()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating client for global bucket: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE this isn't _technically_ required, but if the `hosts add`
|
|
||||||
// command for this host has been run recently then it might not have
|
|
||||||
// made it into the bootstrap file yet, and so won't be in
|
|
||||||
// `env.Bootstrap`.
|
|
||||||
hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("retrieving host info from garage: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
host, ok := hosts[*name]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("couldn't find host into for %q in garage, has `cryptic-net hosts add` been run yet?", *name)
|
|
||||||
}
|
|
||||||
|
|
||||||
nebulaHostCert, err := nebula.NewHostCert(adm.NebulaCACert, host.Name, host.Nebula.IP)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newBootstrap := bootstrap.Bootstrap{
|
|
||||||
Hosts: hosts,
|
|
||||||
HostName: *name,
|
|
||||||
|
|
||||||
NebulaHostCert: nebulaHostCert,
|
|
||||||
|
|
||||||
GarageRPCSecret: adm.GarageRPCSecret,
|
|
||||||
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials,
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBootstrap.WriteTo(os.Stdout)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var subCmdHosts = subCmd{
|
var subCmdHosts = subCmd{
|
||||||
name: "hosts",
|
name: "hosts",
|
||||||
descr: "Sub-commands having to do with configuration of hosts in the network",
|
descr: "Sub-commands having to do with configuration of hosts in the network",
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
subCmdHostsAdd,
|
subCmdHostsAdd,
|
||||||
subCmdHostsList,
|
|
||||||
subCmdHostsDelete,
|
subCmdHostsDelete,
|
||||||
subCmdHostsMakeBootstrap,
|
subCmdHostsList,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,10 @@ func Main() {
|
|||||||
args: os.Args[1:],
|
args: os.Args[1:],
|
||||||
env: env,
|
env: env,
|
||||||
}.doSubCmd(
|
}.doSubCmd(
|
||||||
|
subCmdAdmin,
|
||||||
subCmdDaemon,
|
subCmdDaemon,
|
||||||
subCmdHosts,
|
|
||||||
subCmdGarage,
|
subCmdGarage,
|
||||||
|
subCmdHosts,
|
||||||
subCmdVersion,
|
subCmdVersion,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
package garage_entrypoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
crypticnet "cryptic-net"
|
|
||||||
"cryptic-net/garage"
|
|
||||||
|
|
||||||
"github.com/cryptic-io/pmux/pmuxlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeChildConf(
|
|
||||||
env *crypticnet.Env,
|
|
||||||
alloc crypticnet.DaemonYmlStorageAllocation,
|
|
||||||
) (string, error) {
|
|
||||||
|
|
||||||
if err := os.MkdirAll(alloc.MetaPath, 0750); err != nil {
|
|
||||||
return "", fmt.Errorf("making directory %q: %w", alloc.MetaPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
thisHost := env.Bootstrap.ThisHost()
|
|
||||||
|
|
||||||
peer := garage.Peer{
|
|
||||||
IP: thisHost.Nebula.IP,
|
|
||||||
RPCPort: alloc.RPCPort,
|
|
||||||
S3APIPort: alloc.S3APIPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, privKey := peer.RPCPeerKey()
|
|
||||||
|
|
||||||
nodeKeyPath := filepath.Join(alloc.MetaPath, "node_key")
|
|
||||||
nodeKeyPubPath := filepath.Join(alloc.MetaPath, "node_keypub")
|
|
||||||
|
|
||||||
if err := os.WriteFile(nodeKeyPath, privKey, 0400); err != nil {
|
|
||||||
return "", fmt.Errorf("writing private key to %q: %w", nodeKeyPath, err)
|
|
||||||
|
|
||||||
} else if err := os.WriteFile(nodeKeyPubPath, pubKey, 0440); err != nil {
|
|
||||||
return "", fmt.Errorf("writing public key to %q: %w", nodeKeyPubPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
garageTomlPath := filepath.Join(
|
|
||||||
env.RuntimeDirPath, fmt.Sprintf("garage-%d.toml", alloc.RPCPort),
|
|
||||||
)
|
|
||||||
|
|
||||||
err := garage.WriteGarageTomlFile(garageTomlPath, garage.GarageTomlData{
|
|
||||||
MetaPath: alloc.MetaPath,
|
|
||||||
DataPath: alloc.DataPath,
|
|
||||||
|
|
||||||
RPCSecret: env.Bootstrap.GarageRPCSecret,
|
|
||||||
|
|
||||||
RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
|
|
||||||
APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)),
|
|
||||||
WebAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.WebPort)),
|
|
||||||
|
|
||||||
BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("creating garage.toml file at %q: %w", garageTomlPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return garageTomlPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForArgs(env *crypticnet.Env, bin string, binArgs ...string) []string {
|
|
||||||
|
|
||||||
thisHost := env.Bootstrap.ThisHost()
|
|
||||||
|
|
||||||
var args []string
|
|
||||||
|
|
||||||
for _, alloc := range env.ThisDaemon().Storage.Allocations {
|
|
||||||
args = append(
|
|
||||||
args,
|
|
||||||
"wait-for",
|
|
||||||
net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
|
|
||||||
"--",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, bin)
|
|
||||||
args = append(args, binArgs...)
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func Main() {
|
|
||||||
|
|
||||||
env, err := crypticnet.ReadEnv()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("reading envvars: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pmuxProcConfigs []pmuxlib.ProcessConfig
|
|
||||||
|
|
||||||
for _, alloc := range env.ThisDaemon().Storage.Allocations {
|
|
||||||
|
|
||||||
childConfPath, err := writeChildConf(env, alloc)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("writing child config file for alloc %+v: %v", alloc, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("wrote config file %q", childConfPath)
|
|
||||||
|
|
||||||
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
|
||||||
Name: fmt.Sprintf("garage-%d", alloc.RPCPort),
|
|
||||||
Cmd: "garage",
|
|
||||||
Args: []string{"-c", childConfPath, "server"},
|
|
||||||
SigKillWait: 1 * time.Minute,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
|
||||||
Name: "garage-apply-layout-diff",
|
|
||||||
Cmd: "bash",
|
|
||||||
Args: waitForArgs(env, "bash", "garage-apply-layout-diff"),
|
|
||||||
NoRestartOn: []int{0},
|
|
||||||
})
|
|
||||||
|
|
||||||
pmuxlib.Run(env.Context, pmuxlib.Config{Processes: pmuxProcConfigs})
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user