github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/cmd/install/install.go (about)

     1  /*
     2  *
     3  * Gosora Installer
     4  * Copyright Azareal 2017 - 2019
     5  *
     6   */
     7  package main
     8  
     9  import (
    10  	"bufio"
    11  	"errors"
    12  	"fmt"
    13  	"os"
    14  	"runtime/debug"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/Azareal/Gosora/install"
    19  )
    20  
    21  var scanner *bufio.Scanner
    22  
    23  var siteShortName string
    24  var siteName string
    25  var siteURL string
    26  var serverPort string
    27  
    28  var defaultAdapter = "mysql"
    29  var defaultHost = "localhost"
    30  var defaultUsername = "root"
    31  var defaultDbname = "gosora"
    32  var defaultSiteShortName = "SN"
    33  var defaultSiteName = "Site Name"
    34  var defaultsiteURL = "localhost"
    35  var defaultServerPort = "80" // 8080's a good one, if you're testing and don't want it to clash with port 80
    36  
    37  func main() {
    38  	// Capture panics instead of closing the window at a superhuman speed before the user can read the message on Windows
    39  	defer func() {
    40  		if r := recover(); r != nil {
    41  			fmt.Println(r)
    42  			debug.PrintStack()
    43  			pressAnyKey()
    44  			return
    45  		}
    46  	}()
    47  
    48  	scanner = bufio.NewScanner(os.Stdin)
    49  	fmt.Println("Welcome to Gosora's Installer")
    50  	fmt.Println("We're going to take you through a few steps to help you get started :)")
    51  	adap, ok := handleDatabaseDetails()
    52  	if !ok {
    53  		err := scanner.Err()
    54  		if err != nil {
    55  			fmt.Println(err)
    56  		} else {
    57  			err = errors.New("Something went wrong!")
    58  		}
    59  		abortError(err)
    60  		return
    61  	}
    62  
    63  	if !getSiteDetails() {
    64  		err := scanner.Err()
    65  		if err != nil {
    66  			fmt.Println(err)
    67  		} else {
    68  			err = errors.New("Something went wrong!")
    69  		}
    70  		abortError(err)
    71  		return
    72  	}
    73  
    74  	err := adap.InitDatabase()
    75  	if err != nil {
    76  		abortError(err)
    77  		return
    78  	}
    79  
    80  	err = adap.TableDefs()
    81  	if err != nil {
    82  		abortError(err)
    83  		return
    84  	}
    85  
    86  	err = adap.CreateAdmin()
    87  	if err != nil {
    88  		abortError(err)
    89  		return
    90  	}
    91  
    92  	err = adap.InitialData()
    93  	if err != nil {
    94  		abortError(err)
    95  		return
    96  	}
    97  
    98  	configContents := []byte(`{
    99  	"Site": {
   100  		"ShortName":"` + siteShortName + `",
   101  		"Name":"` + siteName + `",
   102  		"URL":"` + siteURL + `",
   103  		"Port":"` + serverPort + `",
   104  		"EnableSsl":false,
   105  		"EnableEmails":false,
   106  		"HasProxy":false,
   107  		"Language": "english"
   108  	},
   109  	"Config": {
   110  		"SslPrivkey": "",
   111  		"SslFullchain": "",
   112  		"SMTPServer": "",
   113  		"SMTPUsername": "",
   114  		"SMTPPassword": "",
   115  		"SMTPPort": "25",
   116  
   117  		"MaxRequestSizeStr":"5MB",
   118  		"UserCache":"static",
   119  		"TopicCache":"static",
   120  		"ReplyCache":"static",
   121  		"UserCacheCapacity":180,
   122  		"TopicCacheCapacity":400,
   123  		"ReplyCacheCapacity":20,
   124  		"DefaultPath":"/topics/",
   125  		"DefaultGroup":3,
   126  		"ActivationGroup":5,
   127  		"StaffCSS":"staff_post",
   128  		"DefaultForum":2,
   129  		"MinifyTemplates":true,
   130  		"BuildSlugs":true,
   131  		"ServerCount":1,
   132  		"Noavatar":"https://api.adorable.io/avatars/{width}/{id}.png",
   133  		"ItemsPerPage":25
   134  	},
   135  	"Database": {
   136  		"Adapter": "` + adap.Name() + `",
   137  		"Host": "` + adap.DBHost() + `",
   138  		"Username": "` + adap.DBUsername() + `",
   139  		"Password": "` + adap.DBPassword() + `",
   140  		"Dbname": "` + adap.DBName() + `",
   141  		"Port": "` + adap.DBPort() + `",
   142  
   143  		"TestAdapter": "` + adap.Name() + `",
   144  		"TestHost": "",
   145  		"TestUsername": "",
   146  		"TestPassword": "",
   147  		"TestDbname": "",
   148  		"TestPort": ""
   149  	},
   150  	"Dev": {
   151  		"DebugMode":true,
   152  		"SuperDebug":false
   153  	}
   154  }`)
   155  
   156  	fmt.Println("Opening the configuration file")
   157  	configFile, err := os.Create("./config/config.json")
   158  	if err != nil {
   159  		abortError(err)
   160  		return
   161  	}
   162  
   163  	fmt.Println("Writing to the configuration file...")
   164  	_, err = configFile.Write(configContents)
   165  	if err != nil {
   166  		abortError(err)
   167  		return
   168  	}
   169  
   170  	configFile.Sync()
   171  	configFile.Close()
   172  	fmt.Println("Finished writing to the configuration file")
   173  
   174  	fmt.Println("Yay, you have successfully installed Gosora!")
   175  	fmt.Println("Your name is Admin and you can login with the password 'password'. Don't forget to change it! Seriously. It's really insecure.")
   176  	pressAnyKey()
   177  }
   178  
   179  func abortError(err error) {
   180  	fmt.Println(err)
   181  	fmt.Println("Aborting installation...")
   182  	pressAnyKey()
   183  }
   184  
   185  func handleDatabaseDetails() (adap install.InstallAdapter, ok bool) {
   186  	var dbAdapter string
   187  	var dbHost string
   188  	var dbUsername string
   189  	var dbPassword string
   190  	var dbName string
   191  	// TODO: Let the admin set the database port?
   192  	//var dbPort string
   193  
   194  	for {
   195  		fmt.Println("Which database adapter do you wish to use? mysql or mssql? Default: mysql")
   196  		if !scanner.Scan() {
   197  			return nil, false
   198  		}
   199  		dbAdapter := strings.TrimSpace(scanner.Text())
   200  		if dbAdapter == "" {
   201  			dbAdapter = defaultAdapter
   202  		}
   203  		adap, ok = install.Lookup(dbAdapter)
   204  		if ok {
   205  			break
   206  		}
   207  		fmt.Println("That adapter doesn't exist")
   208  	}
   209  	fmt.Println("Set database adapter to " + dbAdapter)
   210  
   211  	fmt.Println("Database Host? Default: " + defaultHost)
   212  	if !scanner.Scan() {
   213  		return nil, false
   214  	}
   215  	dbHost = scanner.Text()
   216  	if dbHost == "" {
   217  		dbHost = defaultHost
   218  	}
   219  	fmt.Println("Set database host to " + dbHost)
   220  
   221  	fmt.Println("Database Username? Default: " + defaultUsername)
   222  	if !scanner.Scan() {
   223  		return nil, false
   224  	}
   225  	dbUsername = scanner.Text()
   226  	if dbUsername == "" {
   227  		dbUsername = defaultUsername
   228  	}
   229  	fmt.Println("Set database username to " + dbUsername)
   230  
   231  	fmt.Println("Database Password? Default: ''")
   232  	if !scanner.Scan() {
   233  		return nil, false
   234  	}
   235  	dbPassword = scanner.Text()
   236  	if len(dbPassword) == 0 {
   237  		fmt.Println("You didn't set a password for this user. This won't block the installation process, but it might create security issues in the future.")
   238  		fmt.Println("")
   239  	} else {
   240  		fmt.Println("Set password to " + obfuscatePassword(dbPassword))
   241  	}
   242  
   243  	fmt.Println("Database Name? Pick a name you like or one provided to you. Default: " + defaultDbname)
   244  	if !scanner.Scan() {
   245  		return nil, false
   246  	}
   247  	dbName = scanner.Text()
   248  	if dbName == "" {
   249  		dbName = defaultDbname
   250  	}
   251  	fmt.Println("Set database name to " + dbName)
   252  
   253  	adap.SetConfig(dbHost, dbUsername, dbPassword, dbName, adap.DefaultPort())
   254  	return adap, true
   255  }
   256  
   257  func getSiteDetails() bool {
   258  	fmt.Println("Okay. We also need to know some actual information about your site!")
   259  	fmt.Println("What's your site's name? Default: " + defaultSiteName)
   260  	if !scanner.Scan() {
   261  		return false
   262  	}
   263  	siteName = scanner.Text()
   264  	if siteName == "" {
   265  		siteName = defaultSiteName
   266  	}
   267  	fmt.Println("Set the site name to " + siteName)
   268  
   269  	// ? - We could compute this based on the first letter of each word in the site's name, if it's name spans multiple words. I'm not sure how to do this for single word names.
   270  	fmt.Println("Can we have a short abbreviation for your site? Default: " + defaultSiteShortName)
   271  	if !scanner.Scan() {
   272  		return false
   273  	}
   274  	siteShortName = scanner.Text()
   275  	if siteShortName == "" {
   276  		siteShortName = defaultSiteShortName
   277  	}
   278  	fmt.Println("Set the short name to " + siteShortName)
   279  
   280  	fmt.Println("What's your site's url? Default: " + defaultsiteURL)
   281  	if !scanner.Scan() {
   282  		return false
   283  	}
   284  	siteURL = scanner.Text()
   285  	if siteURL == "" {
   286  		siteURL = defaultsiteURL
   287  	}
   288  	fmt.Println("Set the site url to " + siteURL)
   289  
   290  	fmt.Println("What port do you want the server to listen on? If you don't know what this means, you should probably leave it on the default. Default: " + defaultServerPort)
   291  	if !scanner.Scan() {
   292  		return false
   293  	}
   294  	serverPort = scanner.Text()
   295  	if serverPort == "" {
   296  		serverPort = defaultServerPort
   297  	}
   298  	_, err := strconv.Atoi(serverPort)
   299  	if err != nil {
   300  		fmt.Println("That's not a valid number!")
   301  		return false
   302  	}
   303  	fmt.Println("Set the server port to " + serverPort)
   304  	return true
   305  }
   306  
   307  func obfuscatePassword(password string) (out string) {
   308  	for i := 0; i < len(password); i++ {
   309  		out += "*"
   310  	}
   311  	return out
   312  }
   313  
   314  func pressAnyKey() {
   315  	//fmt.Println("Press any key to exit...")
   316  	fmt.Println("Please press enter to exit...")
   317  	for scanner.Scan() {
   318  		_ = scanner.Text()
   319  		return
   320  	}
   321  }