Semaphore/cli/setup/setup.go

248 lines
6.6 KiB
Go
Raw Permalink Normal View History

2021-08-11 17:10:54 +02:00
package setup
import (
"fmt"
"os"
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
2021-08-11 17:10:54 +02:00
"github.com/semaphoreui/semaphore/util"
2021-08-11 17:10:54 +02:00
)
const interactiveSetupBlurb = `
Hello! You will now be guided through a setup to:
1. Set up configuration for a MySQL/MariaDB database
2. Set up a path for your playbooks (auto-created)
3. Run database Migrations
4. Set up initial semaphore user & password
`
func InteractiveRunnerSetup(conf *util.ConfigType) {
askValue("Semaphore server URL", "", &conf.WebHost)
conf.Runner = &util.RunnerConfig{}
askValue("Path to the file where runner token will be stored", "", &conf.Runner.TokenFile)
haveToken := false
askConfirmation("Do you have runner token?", false, &haveToken)
if haveToken {
token := ""
askValue("Runner token", "", &token)
// TODO: write token
}
}
2021-08-11 17:10:54 +02:00
func InteractiveSetup(conf *util.ConfigType) {
fmt.Print(interactiveSetupBlurb)
dbPrompt := `What database to use:
1 - MySQL
2 - BoltDB
2021-08-24 17:20:34 +02:00
3 - PostgreSQL
2021-08-11 17:10:54 +02:00
`
var db int
2021-08-11 18:31:09 +02:00
askValue(dbPrompt, "1", &db)
2021-08-11 17:10:54 +02:00
switch db {
case 1:
2021-08-28 19:57:04 +02:00
conf.Dialect = util.DbDriverMySQL
2021-08-11 18:31:09 +02:00
scanMySQL(conf)
2021-08-11 17:10:54 +02:00
case 2:
2021-08-28 19:57:04 +02:00
conf.Dialect = util.DbDriverBolt
2021-08-11 18:31:09 +02:00
scanBoltDb(conf)
2021-08-24 17:20:34 +02:00
case 3:
2021-08-28 19:57:04 +02:00
conf.Dialect = util.DbDriverPostgres
2021-08-24 17:20:34 +02:00
scanPostgres(conf)
2021-08-11 17:10:54 +02:00
}
defaultPlaybookPath := filepath.Join(os.TempDir(), "semaphore")
2021-08-11 18:31:09 +02:00
askValue("Playbook path", defaultPlaybookPath, &conf.TmpPath)
2021-08-11 17:10:54 +02:00
conf.TmpPath = filepath.Clean(conf.TmpPath)
2023-07-23 02:23:25 +02:00
askValue("Public URL (optional, example: https://example.com/semaphore)", "", &conf.WebHost)
2021-08-11 17:10:54 +02:00
2021-08-11 18:31:09 +02:00
askConfirmation("Enable email alerts?", false, &conf.EmailAlert)
2021-08-11 17:10:54 +02:00
if conf.EmailAlert {
2021-08-11 18:31:09 +02:00
askValue("Mail server host", "localhost", &conf.EmailHost)
askValue("Mail server port", "25", &conf.EmailPort)
askValue("Mail sender address", "semaphore@localhost", &conf.EmailSender)
2021-08-11 17:10:54 +02:00
}
2021-08-11 18:31:09 +02:00
askConfirmation("Enable telegram alerts?", false, &conf.TelegramAlert)
2021-08-11 17:10:54 +02:00
if conf.TelegramAlert {
2021-08-11 18:31:09 +02:00
askValue("Telegram bot token (you can get it from @BotFather)", "", &conf.TelegramToken)
askValue("Telegram chat ID", "", &conf.TelegramChat)
2021-08-11 17:10:54 +02:00
}
2022-04-11 10:29:48 +02:00
askConfirmation("Enable slack alerts?", false, &conf.SlackAlert)
if conf.SlackAlert {
askValue("Slack Webhook URL", "", &conf.SlackUrl)
}
askConfirmation("Enable Rocket.Chat alerts?", false, &conf.RocketChatAlert)
if conf.RocketChatAlert {
askValue("Rocket.Chat Webhook URL", "", &conf.RocketChatUrl)
}
askConfirmation("Enable Microsoft Team Channel alerts?", false, &conf.MicrosoftTeamsAlert)
if conf.MicrosoftTeamsAlert {
askValue("Microsoft Teams Webhook URL", "", &conf.MicrosoftTeamsUrl)
}
2021-08-11 18:31:09 +02:00
askConfirmation("Enable LDAP authentication?", false, &conf.LdapEnable)
2021-08-11 17:10:54 +02:00
if conf.LdapEnable {
conf.LdapMappings = &util.LdapMappings{}
2021-08-11 18:31:09 +02:00
askValue("LDAP server host", "localhost:389", &conf.LdapServer)
askConfirmation("Enable LDAP TLS connection", false, &conf.LdapNeedTLS)
askValue("LDAP DN for bind", "cn=user,ou=users,dc=example", &conf.LdapBindDN)
askValue("Password for LDAP bind user", "pa55w0rd", &conf.LdapBindPassword)
askValue("LDAP DN for user search", "ou=users,dc=example", &conf.LdapSearchDN)
askValue("LDAP search filter", `(uid=%s)`, &conf.LdapSearchFilter)
askValue("LDAP mapping for DN field", "dn", &conf.LdapMappings.DN)
askValue("LDAP mapping for username field", "uid", &conf.LdapMappings.UID)
askValue("LDAP mapping for full name field", "cn", &conf.LdapMappings.CN)
askValue("LDAP mapping for email field", "mail", &conf.LdapMappings.Mail)
2021-08-11 17:10:54 +02:00
}
}
2021-08-11 18:31:09 +02:00
func scanBoltDb(conf *util.ConfigType) {
2021-08-11 17:10:54 +02:00
workingDirectory, err := os.Getwd()
if err != nil {
workingDirectory = os.TempDir()
}
defaultBoltDBPath := filepath.Join(workingDirectory, "database.boltdb")
conf.BoltDb = &util.DbConfig{}
askValue("db filename", defaultBoltDBPath, &conf.BoltDb.Hostname)
2021-08-11 17:10:54 +02:00
}
2021-08-11 18:31:09 +02:00
func scanMySQL(conf *util.ConfigType) {
conf.MySQL = &util.DbConfig{}
askValue("db Hostname", "127.0.0.1:3306", &conf.MySQL.Hostname)
askValue("db User", "root", &conf.MySQL.Username)
askValue("db Password", "", &conf.MySQL.Password)
askValue("db Name", "semaphore", &conf.MySQL.DbName)
2021-08-11 17:10:54 +02:00
}
2021-08-24 17:20:34 +02:00
func scanPostgres(conf *util.ConfigType) {
conf.Postgres = &util.DbConfig{}
askValue("db Hostname", "127.0.0.1:5432", &conf.Postgres.Hostname)
askValue("db User", "root", &conf.Postgres.Username)
askValue("db Password", "", &conf.Postgres.Password)
askValue("db Name", "semaphore", &conf.Postgres.DbName)
if conf.Postgres.Options == nil {
conf.Postgres.Options = make(map[string]string)
}
if _, exists := conf.Postgres.Options["sslmode"]; !exists {
conf.Postgres.Options["sslmode"] = "disable"
}
2021-08-24 17:20:34 +02:00
}
2021-08-11 18:31:09 +02:00
func scanErrorChecker(n int, err error) {
if err != nil && err.Error() != "unexpected newline" {
log.Warn("An input error occurred: " + err.Error())
}
}
2021-08-11 17:10:54 +02:00
type IConfig interface {
ToJSON() ([]byte, error)
}
func SaveConfig(config IConfig, defaultFilename string, requiredConfigPath string) (configPath string) {
if requiredConfigPath == "" {
configDirectory, err := os.Getwd()
2021-08-11 17:10:54 +02:00
if err != nil {
configDirectory, err = os.UserConfigDir()
if err != nil {
// Final fallback
configDirectory = "/etc/semaphore"
}
configDirectory = filepath.Join(configDirectory, "semaphore")
2021-08-11 17:10:54 +02:00
}
2024-09-29 22:29:58 +02:00
askValue("Config output directory", configDirectory, &configDirectory)
configPath = filepath.Join(configDirectory, defaultFilename)
} else {
configPath = requiredConfigPath
2021-08-11 17:10:54 +02:00
}
configDirectory := filepath.Dir(configPath)
2021-08-11 17:10:54 +02:00
fmt.Printf("Running: mkdir -p %v..\n", configDirectory)
var err error
if _, err = os.Stat(configDirectory); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(configDirectory, 0755)
}
}
2021-08-11 17:10:54 +02:00
if err != nil {
log.Panic("Could not create config directory: " + err.Error())
}
// Marshal config to json
bytes, err := config.ToJSON()
if err != nil {
panic(err)
}
2024-03-10 20:07:19 +01:00
if err = os.WriteFile(configPath, bytes, 0644); err != nil {
2021-08-11 17:10:54 +02:00
panic(err)
}
fmt.Printf("Configuration written to %v..\n", configPath)
2021-08-11 18:31:09 +02:00
return
2021-08-11 17:10:54 +02:00
}
2021-08-11 18:31:09 +02:00
func askValue(prompt string, defaultValue string, item interface{}) {
2021-08-11 17:10:54 +02:00
// Print prompt with optional default value
fmt.Print(prompt)
2021-08-11 18:31:09 +02:00
if len(defaultValue) != 0 {
fmt.Print(" (default " + defaultValue + ")")
2021-08-11 17:10:54 +02:00
}
2021-08-11 18:31:09 +02:00
fmt.Print(": ")
2021-08-11 17:10:54 +02:00
2021-08-11 19:19:07 +02:00
_, _ = fmt.Sscanln(defaultValue, item)
2021-08-11 18:31:09 +02:00
scanErrorChecker(fmt.Scanln(item))
2021-08-11 17:10:54 +02:00
// Empty line after prompt
fmt.Println("")
}
2021-08-11 18:31:09 +02:00
func askConfirmation(prompt string, defaultValue bool, item *bool) {
2021-08-11 17:10:54 +02:00
defString := "yes"
2021-08-11 18:31:09 +02:00
if !defaultValue {
2021-08-11 17:10:54 +02:00
defString = "no"
}
2021-08-11 18:31:09 +02:00
fmt.Print(prompt + " (yes/no) (default " + defString + "): ")
2021-08-11 17:10:54 +02:00
2021-08-11 18:31:09 +02:00
var answer string
scanErrorChecker(fmt.Scanln(&answer))
2021-08-11 17:10:54 +02:00
2021-08-11 18:31:09 +02:00
switch strings.ToLower(answer) {
2021-08-11 17:10:54 +02:00
case "y", "yes":
*item = true
case "n", "no":
*item = false
default:
2021-08-11 18:31:09 +02:00
*item = defaultValue
2021-08-11 17:10:54 +02:00
}
// Empty line after prompt
fmt.Println("")
}