/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package answercmd import ( "context" "fmt" "os" "strings" "github.com/apache/answer/internal/base/conf" "github.com/apache/answer/internal/base/path" "github.com/apache/answer/internal/cli" "github.com/apache/answer/internal/install" "github.com/apache/answer/internal/migrations" "github.com/apache/answer/plugin" "github.com/segmentfault/pacman/log" "github.com/spf13/cobra" ) var ( // dataDirPath save all answer application data in this directory. like config file, upload file... dataDirPath string // dumpDataPath dump data path dumpDataPath string // place to build new answer buildDir string // plugins needed to build in answer application buildWithPlugins []string // build output path buildOutput string // This config is used to upgrade the database from a specific version manually. // If you want to upgrade the database to version 1.1.0, you can use `answer upgrade -f v1.1.0`. upgradeVersion string // The fields that need to be set to the default value configFields []string // i18nSourcePath i18n from path i18nSourcePath string // i18nTargetPath i18n to path i18nTargetPath string // resetPasswordEmail user email for password reset resetPasswordEmail string // resetPasswordPassword new password for password reset resetPasswordPassword string ) func init() { rootCmd.Version = fmt.Sprintf("%s\nrevision: %s\nbuild time: %s", Version, Revision, Time) rootCmd.PersistentFlags().StringVarP(&dataDirPath, "data-path", "C", "/data/", "data path, eg: -C ./data/") dumpCmd.Flags().StringVarP(&dumpDataPath, "path", "p", "./", "dump data path, eg: -p ./dump/data/") buildCmd.Flags().StringSliceVarP(&buildWithPlugins, "with", "w", []string{}, "plugins needed to build") buildCmd.Flags().StringVarP(&buildOutput, "output", "o", "", "build output path") buildCmd.Flags().StringVarP(&buildDir, "build-dir", "b", "", "dir for build process") upgradeCmd.Flags().StringVarP(&upgradeVersion, "from", "f", "", "upgrade from specific version, eg: -f v1.1.0") configCmd.Flags().StringSliceVarP(&configFields, "with", "w", []string{}, "the fields that need to be set to the default value, eg: -w allow_password_login") i18nCmd.Flags().StringVarP(&i18nSourcePath, "source", "s", "", "i18n source path, eg: -s ./i18n/source") i18nCmd.Flags().StringVarP(&i18nTargetPath, "target", "t", "", "i18n target path, eg: -t ./i18n/target") resetPasswordCmd.Flags().StringVarP(&resetPasswordEmail, "email", "e", "", "user email address") resetPasswordCmd.Flags().StringVarP(&resetPasswordPassword, "password", "p", "", "new password (not recommended, will be recorded in shell history)") for _, cmd := range []*cobra.Command{initCmd, checkCmd, runCmd, dumpCmd, upgradeCmd, buildCmd, pluginCmd, configCmd, i18nCmd, resetPasswordCmd} { rootCmd.AddCommand(cmd) } } var ( rootCmd = &cobra.Command{ Use: "answer", Short: "Answer is a minimalist open source Q&A community.", Long: `Answer is a minimalist open source Q&A community. To run answer, use: - 'answer init' to initialize the required environment. - 'answer run' to launch application.`, } runCmd = &cobra.Command{ Use: "run", Short: "Run Answer", Long: `Start running Answer`, Run: func(_ *cobra.Command, _ []string) { path.FormatAllPath(dataDirPath) fmt.Println("config file path: ", path.GetConfigFilePath()) fmt.Println("Answer is starting..........................") runApp() }, } initCmd = &cobra.Command{ Use: "init", Short: "Initialize Answer", Long: `Initialize Answer with specified configuration`, Run: func(_ *cobra.Command, _ []string) { // check config file and database. if config file exists and database is already created, init done cli.InstallAllInitialEnvironment(dataDirPath) configFileExist := cli.CheckConfigFile(path.GetConfigFilePath()) if configFileExist { fmt.Println("config file exists, try to read the config...") c, err := conf.ReadConfig(path.GetConfigFilePath()) if err != nil { fmt.Println("read config failed: ", err.Error()) return } fmt.Println("config file read successfully, try to connect database...") if cli.CheckDBTableExist(c.Data.Database) { fmt.Println("connect to database successfully and table already exists, do nothing.") return } } // start installation server to install install.Run(path.GetConfigFilePath()) }, } upgradeCmd = &cobra.Command{ Use: "upgrade", Short: "Upgrade Answer", Long: `Upgrade Answer to the latest version`, Run: func(_ *cobra.Command, _ []string) { log.SetLogger(log.NewStdLogger(os.Stdout)) path.FormatAllPath(dataDirPath) cli.InstallI18nBundle(true) c, err := conf.ReadConfig(path.GetConfigFilePath()) if err != nil { fmt.Println("read config failed: ", err.Error()) return } if err = migrations.Migrate(c.Debug, c.Data.Database, c.Data.Cache, upgradeVersion); err != nil { fmt.Println("migrate failed: ", err.Error()) return } fmt.Println("upgrade done") }, } dumpCmd = &cobra.Command{ Use: "dump", Short: "Back up data", Long: `Back up database into an SQL file`, Run: func(_ *cobra.Command, _ []string) { fmt.Println("Answer is backing up data") path.FormatAllPath(dataDirPath) c, err := conf.ReadConfig(path.GetConfigFilePath()) if err != nil { fmt.Println("read config failed: ", err.Error()) return } err = cli.DumpAllData(c.Data.Database, dumpDataPath) if err != nil { fmt.Println("dump failed: ", err.Error()) return } fmt.Println("Answer backed up the data successfully.") }, } checkCmd = &cobra.Command{ Use: "check", Short: "Check the required environment", Long: `Check if the current environment meets the startup requirements`, Run: func(_ *cobra.Command, _ []string) { path.FormatAllPath(dataDirPath) fmt.Println("Start checking the required environment...") if cli.CheckConfigFile(path.GetConfigFilePath()) { fmt.Println("config file exists [✔]") } else { fmt.Println("config file not exists [x]") } if cli.CheckUploadDir() { fmt.Println("upload directory exists [✔]") } else { fmt.Println("upload directory not exists [x]") } c, err := conf.ReadConfig(path.GetConfigFilePath()) if err != nil { fmt.Println("read config failed: ", err.Error()) return } if cli.CheckDBConnection(c.Data.Database) { fmt.Println("db connection successfully [✔]") } else { fmt.Println("db connection failed [x]") } fmt.Println("check environment all done") }, } buildCmd = &cobra.Command{ Use: "build", Short: "Build Answer with plugins", Long: `Build a new Answer with plugins that you need`, Run: func(_ *cobra.Command, _ []string) { fmt.Printf("try to build a new answer with plugins:\n%s\n", strings.Join(buildWithPlugins, "\n")) err := cli.BuildNewAnswer(buildDir, buildOutput, buildWithPlugins, cli.OriginalAnswerInfo{ Version: Version, Revision: Revision, Time: Time, }) if err != nil { fmt.Printf("build failed %v\n", err) os.Exit(1) } fmt.Printf("build new answer successfully %s\n", buildOutput) }, } pluginCmd = &cobra.Command{ Use: "plugin", Short: "Print all plugins packed in the binary", Long: `Print all plugins packed in the binary`, Run: func(_ *cobra.Command, _ []string) { _ = plugin.CallBase(func(base plugin.Base) error { info := base.Info() fmt.Printf("%s[%s] made by %s\n", info.SlugName, info.Version, info.Author) return nil }) }, } configCmd = &cobra.Command{ Use: "config", Short: "Set some config to default value", Long: `Set some config to default value`, Run: func(_ *cobra.Command, _ []string) { path.FormatAllPath(dataDirPath) c, err := conf.ReadConfig(path.GetConfigFilePath()) if err != nil { fmt.Println("read config failed: ", err.Error()) return } field := &cli.ConfigField{} fmt.Println(configFields) if len(configFields) > 0 { switch configFields[0] { case "allow_password_login": field.AllowPasswordLogin = true case "deactivate_plugin": if len(configFields) > 1 { field.DeactivatePluginSlugName = configFields[1] } default: fmt.Printf("field %s not support\n", configFields[0]) } } err = cli.SetDefaultConfig(c.Data.Database, c.Data.Cache, field) if err != nil { fmt.Println("set default config failed: ", err.Error()) } else { fmt.Println("set default config successfully") } }, } i18nCmd = &cobra.Command{ Use: "i18n", Short: "Overwrite i18n files", Long: `Merge i18n files from plugins to original i18n files. It will overwrite the original i18n files`, Run: func(_ *cobra.Command, _ []string) { if err := cli.ReplaceI18nFilesLocal(i18nTargetPath); err != nil { fmt.Printf("replace i18n files failed %v\n", err) } else { fmt.Printf("replace i18n files successfully\n") } fmt.Printf("try to merge i18n files from %q to %q\n", i18nSourcePath, i18nTargetPath) if err := cli.MergeI18nFilesLocal(i18nTargetPath, i18nSourcePath); err != nil { fmt.Printf("merge i18n files failed %v\n", err) } else { fmt.Printf("merge i18n files successfully\n") } }, } resetPasswordCmd = &cobra.Command{ Use: "passwd", Aliases: []string{"password", "reset-password"}, Short: "Reset user password", Long: "Reset user password by email address.", Example: ` # Interactive mode (recommended, safest) answer passwd -C ./answer-data # Specify email only (will prompt for password securely) answer passwd -C ./answer-data --email user@example.com answer passwd -C ./answer-data -e user@example.com # Specify email and password (NOT recommended, will be recorded in shell history) answer passwd -C ./answer-data -e user@example.com -p newpassword123`, Run: func(cmd *cobra.Command, args []string) { opts := &cli.ResetPasswordOptions{ Email: resetPasswordEmail, Password: resetPasswordPassword, } if err := cli.ResetPassword(context.Background(), dataDirPath, opts); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } }, } ) // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main(). It only needs to happen once to the rootCmd. func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) } }