package cmd import ( "io/fs" "path/filepath" wisski_distillery "github.com/FAU-CDI/wisski-distillery" "github.com/FAU-CDI/wisski-distillery/internal/bootstrap" "github.com/FAU-CDI/wisski-distillery/internal/cli" "github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/tkw1536/goprogram/exit" ) // Bootstrap is the 'bootstrap' command var Bootstrap wisski_distillery.Command = cBootstrap{} type cBootstrap struct { Directory string `short:"r" long:"root-directory" description:"path to the root deployment directory" default:"/var/www/deploy"` Hostname string `short:"h" long:"hostname" description:"default hostname of the distillery (default: system hostname)"` } func (cBootstrap) Description() wisski_distillery.Description { return wisski_distillery.Description{ Requirements: cli.Requirements{ NeedsDistillery: false, }, Command: "bootstrap", Description: "Bootstraps the installation of a Distillery System", } } var errBootstrapDifferent = exit.Error{ Message: "refusing to bootstrap: base directory is already set to %s.", ExitCode: exit.ExitGeneric, } var errBootstrapFailedToCreateDirectory = exit.Error{ Message: "failed to create directory %s", ExitCode: exit.ExitGeneric, } var errBootstrapFailedToSaveDirectory = exit.Error{ Message: "failed to register base directory: %s", ExitCode: exit.ExitGeneric, } var errBoostrapFailedToCopyExe = exit.Error{ Message: "failed to copy wdcli executable: %s", ExitCode: exit.ExitGeneric, } var errBootstrapWriteConfig = exit.Error{ Message: "failed to write configuration file: %s", ExitCode: exit.ExitGeneric, } var errBootstrapOpenConfig = exit.Error{ Message: "failed to open configuration file: %s", ExitCode: exit.ExitGeneric, } var errBootstrapCreateFile = exit.Error{ Message: "failed to touch configuration file: %s", ExitCode: exit.ExitGeneric, } func (bs cBootstrap) Run(context wisski_distillery.Context) error { // installation environment is the native environment! // TODO: Should this be configurable? env := new(environment.Native) root := bs.Directory // check that we didn't get a different base directory { got, err := cli.ReadBaseDirectory(env) if err == nil && got != "" && got != root { return errBootstrapDifferent.WithMessageF(got) } } { logging.LogMessage(context.Stderr, "Creating root deployment directory") if err := env.MkdirAll(root, environment.DefaultDirPerm); err != nil { return errBootstrapFailedToCreateDirectory.WithMessageF(root) } if err := cli.WriteBaseDirectory(env, root); err != nil { return errBootstrapFailedToSaveDirectory.WithMessageF(root) } context.Println(root) } // TODO: Should we read an existing configuration file? wdcliPath := filepath.Join(root, bootstrap.Executable) envPath := filepath.Join(root, bootstrap.ConfigFile) // setup a new template for the configuration file! var tpl config.Template tpl.DeployRoot = bs.Directory tpl.DefaultDomain = bs.Hostname // and use thge defaults if err := tpl.SetDefaults(env); err != nil { return errBootstrapWriteConfig.WithMessageF(err) } { logging.LogMessage(context.Stderr, "Copying over wdcli executable") exe, err := env.Executable() if err != nil { return errBoostrapFailedToCopyExe.WithMessageF(err) } err = fsx.CopyFile(context.Context, env, wdcliPath, exe) if err != nil && err != fsx.ErrCopySameFile { return errBoostrapFailedToCopyExe.WithMessageF(err) } context.Println(wdcliPath) } { if !fsx.IsFile(env, envPath) { if err := logging.LogOperation(func() error { env, err := env.Create(envPath, environment.DefaultFilePerm) if err != nil { return err } defer env.Close() return tpl.MarshalTo(env) }, context.Stderr, "Installing configuration file"); err != nil { return errBootstrapWriteConfig.WithMessageF(err) } if err := logging.LogOperation(func() error { context.Println(tpl.SelfOverridesFile) if err := environment.WriteFile( env, tpl.SelfOverridesFile, bootstrap.DefaultOverridesJSON, fs.ModePerm, ); err != nil { return err } context.Println(tpl.AuthorizedKeys) if err := environment.WriteFile( env, tpl.AuthorizedKeys, bootstrap.DefaultAuthorizedKeys, fs.ModePerm, ); err != nil { return err } context.Println(tpl.SelfResolverBlockFile) if err := environment.WriteFile( env, tpl.SelfResolverBlockFile, bootstrap.DefaultResolverBlockedTXT, fs.ModePerm, ); err != nil { return err } return nil }, context.Stderr, "Creating additional config files"); err != nil { return errBootstrapCreateFile.WithMessageF(err) } } } // re-read the configuration and print it! logging.LogMessage(context.Stderr, "Configuration is now complete") f, err := env.Open(envPath) if err != nil { return errBootstrapOpenConfig.WithMessageF(err) } defer f.Close() var cfg config.Config if err := cfg.Unmarshal(env, f); err != nil { return errBootstrapOpenConfig.WithMessageF(err) } context.Println(cfg) // Tell the user how to proceed logging.LogMessage(context.Stderr, "Bootstrap is complete") context.Printf("Adjust the configuration file at %s\n", envPath) context.Printf("Then make sure 'docker compose' is installed.\n") context.Printf("Finally grab a GraphDB zipped source file and run:\n") context.Printf("%s system_update /path/to/graphdb.zip\n", wdcliPath) return nil }