From 08597cef4b885468c4160b5d1b8733492a187516 Mon Sep 17 00:00:00 2001 From: Wanjohi Ryan <71614375+wanjohiryan@users.noreply.github.com> Date: Mon, 8 Jul 2024 04:06:28 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Add=20viper=20config=20and?= =?UTF-8?q?=20basic=20arguments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .nestri.yml | 11 +++ cmd/root.go | 43 +++++++- cmd/run.go | 279 +++++++++------------------------------------------- 3 files changed, 100 insertions(+), 233 deletions(-) create mode 100644 .nestri.yml diff --git a/.nestri.yml b/.nestri.yml new file mode 100644 index 0000000..edb3978 --- /dev/null +++ b/.nestri.yml @@ -0,0 +1,11 @@ +version: "0.1" + +games: + CyberPunk2047: + directory: /path/to/game + executable: CyberPunk2047.exe + gpu: 1 + vendor: GPUidhere #(ex: vendor:N) e.g. nvidia:0 or amd:1 + resolution: + height: 1080 + width: 1920 diff --git a/cmd/root.go b/cmd/root.go index d96a2a2..68d0f7c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,14 +5,18 @@ package cmd import ( _ "embed" + "fmt" "os" "github.com/spf13/cobra" + "github.com/spf13/viper" ) //go:embed nestri.ascii var art string +var cfgFile string + type GameConfig struct { Directory string Executable string @@ -44,12 +48,49 @@ func Execute() { } func init() { + cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.nestri.yaml)") // Cobra also supports local flags, which will only run // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Search for config in the current directory + viper.AddConfigPath(".") + viper.SetConfigName(".nestri") + viper.SetConfigType("yaml") + + // If not found in current directory, check in $HOME/.nestri/ + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".nestri" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".nestri") + } + } + + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err != nil { + fmt.Fprintln(os.Stderr, "Could not find a config file in local directory or in $HOME directory:") + } } diff --git a/cmd/run.go b/cmd/run.go index f9c3fcb..f64d1e2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -5,13 +5,14 @@ import ( "runtime" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // this is the "nestri run" subcommand, takes no arguments for now var runCmd = &cobra.Command{ - Use: "run [options] [game]", + Use: "run", Short: "Run a game using nestri", - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if runtime.GOOS != "linux" { //make sure os is linux @@ -31,230 +32,33 @@ var runCmd = &cobra.Command{ game := args[len(args)-1] // Load game configuration - // var gameConfig GameConfig - // if err := viper.UnmarshalKey(fmt.Sprintf("games.%s", game), &gameConfig); err != nil { - // return fmt.Errorf("error parsing game configuration: %w", err) - // } - - // // Override config with command-line flags - // cmd.Flags().Visit(func(f *pflag.Flag) { - // switch f.Name { - // case "directory": - // gameConfig.Directory = viper.GetString(f.Name) - // case "executable": - // gameConfig.Executable = viper.GetString(f.Name) - // case "gpu": - // gameConfig.GPU = viper.GetInt(f.Name) - // case "vendor": - // gameConfig.Vendor = viper.GetString(f.Name) - // case "resolution-height": - // gameConfig.Resolution.Height = viper.GetInt(f.Name) - // case "resolution-width": - // gameConfig.Resolution.Width = viper.GetInt(f.Name) - // } - // }) - - fmt.Printf("Running game: %s\n\n", game) - - // cli, err := client.NewClientWithOpts(client.FromEnv) - // if err != nil { - // return fmt.Errorf("error creating Docker client: %w", err) - // } - - // ctx := context.Background() - - // var game string - // if len(args) > 0 { - // game = args[0] - // viper.Set("game", game) - // viper.WriteConfig() - // } else { - // game = viper.GetString("game") - // if filepath.Ext(game) != ".exe" { - // return fmt.Errorf("Make sure the game is a .exe") - // } - // if game == "" { - // return fmt.Errorf("no game specified and no previous game selected") - // } - // } - - // fmt.Printf("Running game: %s\n\n", game) - - // cli, err := client.NewClientWithOpts() - // if err != nil { - // panic(err) - // } - - // ctx := context.Background() - // resp, err := cli.ContainerCreate(ctx, &container.Config{ - // Image: "hello-world", - // }, nil, nil, nil, "hello-world") - - // if err != nil { - // panic(err) - // } - - // if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { - // panic(err) - // } - - // // Attach to the container to get logs - // out, err := cli.ContainerLogs(ctx, resp.ID, container.LogsOptions{ShowStdout: true, ShowStderr: true, Follow: true}) - // if err != nil { - // fmt.Printf("Error attaching to container logs: %s\n", err) - // } - // defer out.Close() - - // // Copy the logs to stdout and stderr - // stdcopy.StdCopy(os.Stdout, os.Stderr, out) - - // // Wait for the container to finish - // statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) - // select { - // case err := <-errCh: - // if err != nil { - // fmt.Printf("Error waiting for container: %s\n", err) - // } - // case <-statusCh: - // fmt.Println("Container finished") - // } - // // Clean up the container - // if err := cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{}); err != nil { - // fmt.Printf("Error removing container: %s\n", err) - // } - // if gpu > 0 { - // fmt.Print("Using gpu %s\n", gpu) - // } - // if hdr { - // fmt.Println("Enabling HDR mode") - // } - - //get linux version - // versionCmd := exec.Command("grep", "VERSION", "/etc/os-release") - // versionOutput, err := versionCmd.CombinedOutput() - // if err != nil { - // return fmt.Errorf("error getting linux version:") - // } - // fmt.Printf("Linux version:\n%s\n", string(versionOutput)) - - // //Step 1: change to games dir - // fmt.Println("changing to game dir.") //this is a temp command for debug as well as leads to a hardcoded dir - - // HomeDir, err := os.UserHomeDir() - // if err != nil { - // return fmt.Errorf("error getting home directory %v\n", err) - // } - - // err = os.Chdir(fmt.Sprintf("%s/game", HomeDir)) - // if err != nil { - // return fmt.Errorf("error changing directory: %v\n", err) - // } - // //verify we are in game dir - // dir, err := os.Getwd() - // if err != nil { - // return fmt.Errorf("error getting current directory: %v\n", err) - // } - // fmt.Printf("Current directory: %s\n\n", dir) - - // //list games dir - // listDir := exec.Command("ls", "-la", ".") - // listDirOutput, err := listDir.CombinedOutput() - // if err != nil { - // fmt.Errorf("error listing games: %v\n") - // } - // fmt.Printf("List of Games: \n%s\n", listDirOutput) - - // //step 2: Generate a Session ID - // //generate id - // SID := exec.Command("bash", "-c", "head /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | head -c 16") - - // //save output to variable - // output, err := SID.Output() - // if err != nil { - // fmt.Errorf("Error generating Session ID: %v\n", err) - // } - // sessionID := strings.TrimSpace(string(output)) - // fmt.Printf("Your Session ID is: %s\n\n", sessionID) - - // //step 3: Launch netris server - // fmt.Println("Installing Netris/Launching Netris Server") - // checkRunning := exec.Command("sudo", "docker", "ps", "-q", "-f", "name=netris") - // containerId, err := checkRunning.Output() - // if err != nil { - // return fmt.Errorf("error checking running Docker container: %v", err) - // } - - // if len(containerId) == 0 { - // checkExisting := exec.Command("sudo", "docker", "ps", "-aq", "-f", "name=netris") - // containerId, err = checkExisting.Output() - // if err != nil { - // return fmt.Errorf("error checking for existing docker container: %v", err) - // } - - // if len(containerId) == 0 { - // installCmd := exec.Command( - // "sudo", "docker", "run", "-d", "--gpus", "all", "--device=/dev/dri", - // "--name", "netris", "-it", "--entrypoint", "/bin/bash", - // "-e", fmt.Sprintf("SESSION_ID=%s", sessionID), - // "-v", fmt.Sprintf("%s:/game", dir), "-p", "8080:8080/udp", - // "--cap-add=SYS_NICE", "--cap-add=SYS_ADMIN", "ghcr.io/netrisdotme/netris/server:nightly", - // ) - // installCmd.Stdout = os.Stdout - // installCmd.Stderr = os.Stderr - - // if err := installCmd.Run(); err != nil { - // return fmt.Errorf("error running docker command: %v", err) - // } - // } else { - // startContainer := exec.Command("sudo", "docker", "start", "netris") - // startContainer.Stdout = os.Stdout - // startContainer.Stderr = os.Stderr - - // if err := startContainer.Run(); err != nil { - // return fmt.Errorf("error starting existing Docker container: %v", err) - // } - // } - // } - - // //main part of step 4: - // //start netris server - - // fmt.Println("starting netris server\n\n") - // checkFileCmd := exec.Command("sudo", "docker", "exec", "netris", "ls", "-la", "/tmp") - // output, err = checkFileCmd.Output() - // if err != nil { - // return fmt.Errorf("error checking /tmp dir in docker container: %v\n", err) - // } - - // if !strings.Contains(string(output), ".X11-unix") { - // startupCmd := exec.Command("sudo", "docker", "exec", "netris", "/etc/startup.sh", ">", "/dev/null", "&") - // startupCmd.Stdout = os.Stdout - // startupCmd.Stderr = os.Stderr - - // if err := startupCmd.Run(); err != nil { - // return fmt.Errorf("error running startup command: %v\n", err) - // } + var gameConfig GameConfig + if err := viper.UnmarshalKey(fmt.Sprintf("games.%s", game), &gameConfig); err != nil { + return fmt.Errorf("error parsing game configuration: %w", err) + } - // for { - // time.Sleep(7 * time.Minute) - // output, err := checkFileCmd.Output() - // if err != nil { - // return fmt.Errorf("error checking /tmp directory in container: %v\n", err) - // } - // if strings.Contains(string(output), ".X11-unix") { - // break - // } - // } - // } + flags := cmd.Flags() - // gameCmd := fmt.Sprintf("netris-proton -pr %s", game) - // execCmd := exec.Command("sudo", "docker", "exec", "netris", gameCmd) - // execCmd.Stdout = os.Stdout - // execCmd.Stderr = os.Stderr + if flags.Changed("directory") || flags.Changed("d") { + gameConfig.Directory, _ = flags.GetString("directory") + } + if flags.Changed("executable") || flags.Changed("x") { + gameConfig.Executable, _ = flags.GetString("executable") + } + if flags.Changed("gpu") { + gameConfig.GPU, _ = flags.GetInt("gpu") + } + if flags.Changed("vendor") || flags.Changed("v") { + gameConfig.Vendor, _ = flags.GetString("vendor") + } + if flags.Changed("height") || flags.Changed("H") { + gameConfig.Resolution.Height, _ = flags.GetInt("height") + } + if flags.Changed("width") || flags.Changed("W") { + gameConfig.Resolution.Width, _ = flags.GetInt("width") + } - // if err := execCmd.Run(); err != nil { - // return fmt.Errorf("error executing game command in docker container: %v\n", err) - // } + fmt.Println("Game config:", gameConfig) return nil }, @@ -263,13 +67,24 @@ var runCmd = &cobra.Command{ func init() { rootCmd.AddCommand(runCmd) - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // runCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // runCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + runCmd.Flags().StringP("directory", "d", "", "Game directory") + runCmd.Flags().StringP("executable", "x", "", "Game executable") + runCmd.Flags().Int("gpu", 0, "GPU number") + runCmd.Flags().StringP("vendor", "v", "", "GPU vendor") + runCmd.Flags().IntP("height", "H", 1080, "Screen height") + runCmd.Flags().IntP("width", "W", 1920, "Screen width") + + // viper.BindPFlag("directory", runCmd.Flags().Lookup("directory")) + // viper.BindPFlag("executable", runCmd.Flags().Lookup("executable")) + // viper.BindPFlag("gpu", runCmd.Flags().Lookup("gpu")) + // viper.BindPFlag("vendor", runCmd.Flags().Lookup("vendor")) + // viper.BindPFlag("resolution.height", runCmd.Flags().Lookup("height")) + // viper.BindPFlag("resolution.width", runCmd.Flags().Lookup("width")) + + viper.BindPFlag("games.*.directory", runCmd.Flags().Lookup("directory")) + viper.BindPFlag("games.*.executable", runCmd.Flags().Lookup("executable")) + viper.BindPFlag("games.*.gpu", runCmd.Flags().Lookup("gpu")) + viper.BindPFlag("games.*.vendor", runCmd.Flags().Lookup("vendor")) + viper.BindPFlag("games.*.resolution.height", runCmd.Flags().Lookup("height")) + viper.BindPFlag("games.*.resolution.width", runCmd.Flags().Lookup("width")) }