diff --git a/ask-confirmation.go b/ask-confirmation.go index 4542e12..126599e 100644 --- a/ask-confirmation.go +++ b/ask-confirmation.go @@ -2,15 +2,14 @@ package main import ( "fmt" - "log" ) -func askForConfirmation() bool { +func askForConfirmation(withPrefix bool) bool { var response string - _, err := fmt.Scan(&response) - if err != nil { - log.Fatal(err) + if withPrefix == true { + fmt.Printf("> " + colorGreen) } + fmt.Scanln(&response) okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} nokayResponses := []string{"n", "N", "no", "No", "NO"} if containsString(okayResponses, response) { @@ -18,8 +17,8 @@ func askForConfirmation() bool { } else if containsString(nokayResponses, response) { return false } else { - fmt.Println("Please type yes or no and then press enter:") - return askForConfirmation() + fmt.Println(colorRed + "Please type yes or no and then press enter:" + colorReset) + return askForConfirmation(withPrefix) } } diff --git a/authelia.go b/authelia.go index 8a5f5af..f45cd12 100644 --- a/authelia.go +++ b/authelia.go @@ -141,13 +141,13 @@ type AutheliaConfigStruct struct { Notifier AutheliaNotifierStruct `yaml:"notifier"` } -func createAutheliaConfig(rootDomain string, smtpUsername string, smtpHost string, smtpPort int, smptSender string, smtpStartupAddress string, policyForRootDomain string) []byte { +func createAutheliaConfig() { policyRootDomain := "one_factor" - if policyForRootDomain == "two_factor" { + if configPolicyForRootDomain == "two_factor" { policyRootDomain = "two_factor" } - if policyForRootDomain == "bypass" { + if configPolicyForRootDomain == "bypass" { policyRootDomain = "bypass" } autheliaConfig := AutheliaConfigStruct{ @@ -160,7 +160,7 @@ func createAutheliaConfig(rootDomain string, smtpUsername string, smtpHost strin FilePath: "/config/authelia.log", KeepStdout: true, }, - DefaultRedirectionUrl: "https://authelia." + rootDomain, + DefaultRedirectionUrl: "https://authelia." + configRootDomain, Ntp: AutheliaNtpStruct{ Address: "time.cloudflare.com:123", Version: 3, @@ -204,15 +204,15 @@ func createAutheliaConfig(rootDomain string, smtpUsername string, smtpHost strin DefaultPolicy: "deny", Rules: []AutheliaAccessControlRulesStruct{ { - Domain: []string{"authelia." + rootDomain}, + Domain: []string{"authelia." + configRootDomain}, Policy: "bypass", }, { - Domain: []string{rootDomain}, + Domain: []string{configRootDomain}, Policy: policyRootDomain, }, { - Domain: []string{"traefik." + rootDomain}, + Domain: []string{"traefik." + configRootDomain}, Policy: "two_factor", Subject: []string{"group:admins", "group:traefik"}, }, @@ -229,7 +229,7 @@ func createAutheliaConfig(rootDomain string, smtpUsername string, smtpHost strin Name: "authelia_session", Expiration: 3600, Inactivity: 300, - Domain: rootDomain, + Domain: configRootDomain, }, Regulation: AutheliaRegulationStruct{ MaxRetries: 3, @@ -258,12 +258,12 @@ func createAutheliaConfig(rootDomain string, smtpUsername string, smtpHost strin Notifier: AutheliaNotifierStruct{ DisableStartupCheck: false, Smtp: AutheliaNotifierSmtpStruct{ - Username: smtpUsername, - Host: smtpHost, - Port: smtpPort, - Sender: "Authelia <" + smptSender + ">", + Username: configSmtpUsername, + Host: configSmtpHost, + Port: configSmtpPort, + Sender: "Authelia <" + configSmtpSender + ">", Subject: "[Authelia] {title}", - StartupCheckAddress: smtpStartupAddress, + StartupCheckAddress: configSmtpStartupAddress, }, }, } @@ -274,5 +274,13 @@ func createAutheliaConfig(rootDomain string, smtpUsername string, smtpHost strin fmt.Printf("Error while Marshaling. %v", err) } - return yamlData + writeFile("AppData/authelia", "configuration.yml", yamlData, 0644) + + // Secrets + writeFile("secrets", "authelia_notifier_smtp_password", []byte(configSmtpPassword), 0600) + writeFile("secrets", "authelia_jwt_secret", []byte(randomString(32)), 0600) + writeFile("secrets", "authelia_session_secret", []byte(randomString(32)), 0600) + writeFile("secrets", "authelia_storage_encryption_key", []byte(randomString(32)), 0600) + writeFile("secrets", "mysql_password", []byte(randomString(16)), 0600) + writeFile("secrets", "mysql_root_password", []byte(randomString(16)), 0600) } diff --git a/build/traefik-installer b/build/traefik-installer index e1bdbb0..7ce525b 100755 Binary files a/build/traefik-installer and b/build/traefik-installer differ diff --git a/build/traefik-installer.exe b/build/traefik-installer.exe index dbf61f2..5e1b799 100755 Binary files a/build/traefik-installer.exe and b/build/traefik-installer.exe differ diff --git a/docker-compose.go b/docker-compose.go index 23b5455..3ab612d 100644 --- a/docker-compose.go +++ b/docker-compose.go @@ -6,7 +6,7 @@ import ( "gopkg.in/yaml.v3" ) -func createDockerComposeFile(useTraefikHub bool, useCloudflare bool, isLocalEnvironment bool, secureRootDomain bool) []byte { +func createDockerComposeFile() { type IpamConfigSubnetStruct struct { Subnet string `yaml:"subnet"` @@ -152,7 +152,7 @@ func createDockerComposeFile(useTraefikHub bool, useCloudflare bool, isLocalEnvi }, } - if useTraefikHub == true { + if configUseTraefikHub == true { traefikHubServiceDef := ServiceStruct{ ContainerName: "traefik-hub-agent", Image: "ghcr.io/traefik/hub-agent-traefik:v0.7.2", @@ -182,7 +182,7 @@ func createDockerComposeFile(useTraefikHub bool, useCloudflare bool, isLocalEnvi traefikProxyPorts = append(traefikProxyPorts, "0.0.0.0:9900:9900", "0.0.0.0:9901:9901") } - if useCloudflare == true { + if configUseCloudflare == true { cloudflareSecrets := SecretsStruct{ CfEmail: SecretStruct{ File: "./secrets/cf_email", @@ -198,11 +198,11 @@ func createDockerComposeFile(useTraefikHub bool, useCloudflare bool, isLocalEnvi mergo.Merge(&secrets, cloudflareSecrets) } - if isLocalEnvironment == true { + if configIsLocalEnvironment == true { traefikProxyVolumes = append(traefikProxyVolumes, "./certs:/certs:ro") } - if secureRootDomain == true { + if configSecureRootDomain == true { traefikProxyLabels = append(traefikProxyLabels, "traefik.http.routers.traefik-rtr.middlewares=chain-authelia@file") } @@ -438,5 +438,6 @@ func createDockerComposeFile(useTraefikHub bool, useCloudflare bool, isLocalEnvi if err != nil { fmt.Printf("Error while Marshaling. %v", err) } - return yamlData + + writeFile(".", "docker-compose.yml", yamlData, 0644) } diff --git a/functions.go b/functions.go index 74e21e2..134769e 100644 --- a/functions.go +++ b/functions.go @@ -7,6 +7,16 @@ import ( "os" ) +const colorReset = "\033[0m" + +const colorRed = "\033[31m" +const colorGreen = "\033[32m" +const colorYellow = "\033[33m" +const colorBlue = "\033[34m" +const colorPurple = "\033[35m" +const colorCyan = "\033[36m" +const colorWhite = "\033[37m" + func writeFile(filepath string, filename string, content []byte, filePerm os.FileMode) { fullFilepath := filename diff --git a/inputs.go b/inputs.go new file mode 100644 index 0000000..05def3a --- /dev/null +++ b/inputs.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "strconv" +) + +func getTextInput(question string, required bool) string { + var output string + + fmt.Println(question) + fmt.Printf("> " + colorGreen) + fmt.Scanln(&output) + fmt.Println(colorReset) + + if required == true && output == "" { + fmt.Println(colorRed + "An empty value is not allowed here" + colorReset) + output = getTextInput(question, required) + } + + return output +} + +func getNumberInput(question string, required bool) int { + var output int + var configSmtpPortError error + + output, configSmtpPortError = strconv.Atoi(getTextInput(question, required)) + if configSmtpPortError != nil { + fmt.Println(colorRed + "Please enter a valid port" + colorReset) + output = getNumberInput(question, required) + } + + return output +} + +func getConfirmInput(question string) bool { + fmt.Println(question) + output := askForConfirmation(true) + fmt.Println(colorReset) + + return output +} + +func printSectionHeader(title string) { + fmt.Println("\n\n\n --- " + colorCyan + title + colorReset + " --- \n") +} diff --git a/main.go b/main.go index d3119fc..c8d9785 100644 --- a/main.go +++ b/main.go @@ -6,139 +6,91 @@ import ( var appVersion = "1.0.0" -func main() { - - var rootDomain string - var useTraefikHub = false - var useCloudflare = false - var timezone = "Europe/Berlin" - var cloudflareEmail = "" - var cloudflareApiKey = "" - var traefikHubKey = "" - var secureRootDomain bool - var secureExtendedRootDomain bool - var smtpUsername string - var smtpPassword string - var smtpHost string - var smtpPort int - var smtpSender string - var smtpStartupAddress string - var policyForRootDomain = "bypass" - var isLocalEnvironment = true - - fmt.Println("#################") - fmt.Println("") - fmt.Println("traefik installer version: " + appVersion) - fmt.Println("") - fmt.Println("#################") - - fmt.Println("What is your Timezone?") - fmt.Scan(&timezone) +var configRootDomain string +var configUseTraefikHub = false +var configUseCloudflare = false +var configTimezone = "Europe/Berlin" +var configCloudflareEmail = "" +var configCloudflareApiKey = "" +var configTraefikHubKey = "" +var configSecureRootDomain bool +var configSecureExtendedRootDomain bool +var configSmtpUsername string +var configSmtpPassword string +var configSmtpHost string +var configSmtpPort int +var configSmtpSender string +var configSmtpStartupAddress string +var configPolicyForRootDomain = "bypass" +var configIsLocalEnvironment = true - fmt.Println("Is this a local environment (not exposed to the internet)?") - isLocalEnvironment = askForConfirmation() - - if isLocalEnvironment == false { - fmt.Println("What is your root domain?") - fmt.Scan(&rootDomain) +func main() { + showStartMessage() - fmt.Println("Do you want to use Cloudflare?") - useCloudflare = askForConfirmation() + configTimezone = getTextInput("What is your "+colorYellow+"Timezone"+colorReset+"?", true) + configIsLocalEnvironment = getConfirmInput("Is this a " + colorYellow + "local environment" + colorReset + " (not exposed to the internet)?") - if useCloudflare == true { - fmt.Println("What is your Cloudflare email address?") - fmt.Scan(&cloudflareEmail) + if configIsLocalEnvironment == false { + configRootDomain = getTextInput("What is your "+colorYellow+"root domain"+colorReset+"?", true) + configUseCloudflare = getConfirmInput("Do you want to " + colorYellow + "use Cloudflare" + colorReset + "?") - fmt.Println("What is your Cloudflare Global API-Key?") - fmt.Scan(&cloudflareApiKey) + if configUseCloudflare == true { + configCloudflareEmail = getTextInput("What is your "+colorYellow+"Cloudflare email address"+colorReset+"?", true) + configCloudflareApiKey = getTextInput("What is your "+colorYellow+"Cloudflare Global API-Key"+colorReset+"?", true) } - fmt.Println("Do you want to use Traefik-Hub?") - useTraefikHub = askForConfirmation() + configUseTraefikHub = getConfirmInput("Do you want to " + colorYellow + "use Traefik-Hub" + colorReset + "?") - if useTraefikHub == true { - fmt.Println("What is your Traefik-Hub Key? Create a new one here https://hub.traefik.io/agents/new") - fmt.Scan(&traefikHubKey) + if configUseTraefikHub == true { + configTraefikHubKey = getTextInput("What is your "+colorYellow+"Traefik-Hub Key"+colorReset+"? Create a new one here "+colorCyan+"https://hub.traefik.io/agents/new"+colorReset, true) } } else { - fmt.Println("Your root domain is: local.dev") - fmt.Println("We use a default cert file. You can find it in the 'certs' folder. Please install the RootCA.crt file.\nAll urls must have the tld '*.local.dev'") + fmt.Println("Your root domain is: " + colorPurple + "local.dev" + colorReset) + fmt.Println("We use a default cert file. You can find it in the '" + colorBlue + "certs" + colorReset + "' folder. Please install the RootCA.crt file.\nAll urls must have the tld '" + colorBlue + "*.local.dev" + colorReset + "'") - fmt.Println("\nCloudflare is disabled") - fmt.Println("\nTraefik-Hub is disabled") + fmt.Println(colorGreen + "\nCloudflare is disabled" + colorReset) + fmt.Println(colorGreen + "\nTraefik-Hub is disabled" + colorReset) - rootDomain = "local.dev" - useCloudflare = false - useTraefikHub = false + configRootDomain = "local.dev" + configUseCloudflare = false + configUseTraefikHub = false } - fmt.Println("") - fmt.Println(" --- Authelia Configuration --- ") + printSectionHeader("Authelia Configuration") - fmt.Println("Should the root domain (" + rootDomain + ") be secured with authelia?") - secureRootDomain = askForConfirmation() + configSecureRootDomain = getConfirmInput("Should the root domain (" + configRootDomain + ") be " + colorYellow + "secured with authelia" + colorReset + "?") - if secureRootDomain == true { - fmt.Println("Should Two-Factor be used instead of One-Factor authentication for the root domain (" + rootDomain + ")?") - secureExtendedRootDomain = askForConfirmation() + if configSecureRootDomain == true { + configSecureExtendedRootDomain = getConfirmInput("Should " + colorYellow + "Two-Factor" + colorReset + " be used instead of One-Factor authentication for the root domain (" + configRootDomain + ")?") - if secureExtendedRootDomain == true { - policyForRootDomain = "two_factor" + if configSecureExtendedRootDomain == true { + configPolicyForRootDomain = "two_factor" } else { - policyForRootDomain = "one_factor" + configPolicyForRootDomain = "one_factor" } } else { - policyForRootDomain = "bypass" + configPolicyForRootDomain = "bypass" } - fmt.Println("") - fmt.Println(" --- SMTP-Settings for Authelia --- ") - - fmt.Println("SMTP-Host:") - fmt.Scan(&smtpHost) - - fmt.Println("SMTP-Port:") - fmt.Scan(&smtpPort) - - fmt.Println("SMTP-Username:") - fmt.Scan(&smtpUsername) - - fmt.Println("SMTP-Password:") - fmt.Scan(&smtpPassword) - - fmt.Println("SMTP-Sender Address:") - fmt.Scan(&smtpSender) - - fmt.Println("SMTP-Startup Check Address:") - fmt.Scan(&smtpStartupAddress) - - traefikConfig := createTraefikFile(rootDomain, useCloudflare, cloudflareEmail, useTraefikHub) - autheliaConfig := createAutheliaConfig(rootDomain, smtpUsername, smtpHost, smtpPort, smtpSender, smtpStartupAddress, policyForRootDomain) - - writeFile("AppData/traefik-proxy", "acme.json", []byte(""), 0600) - writeFile("AppData/traefik-proxy", "traefik.yml", traefikConfig, 0644) - writeFile("AppData/authelia", "configuration.yml", autheliaConfig, 0644) - - // Secrets - writeFile("secrets", "authelia_notifier_smtp_password", []byte(smtpPassword), 0600) - writeFile("secrets", "authelia_jwt_secret", []byte(randomString(32)), 0600) - writeFile("secrets", "authelia_session_secret", []byte(randomString(32)), 0600) - writeFile("secrets", "authelia_storage_encryption_key", []byte(randomString(32)), 0600) - writeFile("secrets", "mysql_password", []byte(randomString(16)), 0600) - writeFile("secrets", "mysql_root_password", []byte(randomString(16)), 0600) - - if useCloudflare == true { - writeFile("secrets", "cf_email", []byte(cloudflareEmail), 0600) - writeFile("secrets", "cf_api_key", []byte(cloudflareApiKey), 0600) - } - if isLocalEnvironment == true { - traefikTlsDynamicConfig := createTraefikTlsFile() - - writeFile("AppData/traefik-proxy/rules", "local-tls.yml", traefikTlsDynamicConfig, 0644) - } - - dockerComposeConfig := createDockerComposeFile(useTraefikHub, useCloudflare, isLocalEnvironment, secureRootDomain) - writeFile(".", "docker-compose.yml", dockerComposeConfig, 0644) + // SMTP-Settings + printSectionHeader("SMTP-Settings for Authelia") + configSmtpHost = getTextInput(colorYellow+"SMTP-Host"+colorReset+":", true) + configSmtpPort = getNumberInput(colorYellow+"SMTP-Port"+colorReset+":", true) + configSmtpUsername = getTextInput(colorYellow+"SMTP-Username"+colorReset+":", true) + configSmtpPassword = getTextInput(colorYellow+"SMTP-Password"+colorReset+":", true) + configSmtpSender = getTextInput(colorYellow+"SMTP-Sender Address"+colorReset+":", true) + configSmtpStartupAddress = getTextInput(colorYellow+"SMTP-Startup Check Address"+colorReset+":", true) + + createTraefikFile() + createAutheliaConfig() + createDockerComposeFile() + writeFile(".", ".env", []byte(createEnvFile(configRootDomain, configTimezone, configUseTraefikHub, configTraefikHubKey)), 0700) +} - writeFile(".", ".env", []byte(createEnvFile(rootDomain, timezone, useTraefikHub, traefikHubKey)), 0700) +func showStartMessage() { + fmt.Println("#################") + fmt.Println("") + fmt.Println("traefik installer version: " + colorCyan + appVersion + colorReset) + fmt.Println("") + fmt.Println("#################") } diff --git a/traefik.go b/traefik.go index a0d1ef5..b7b9a76 100644 --- a/traefik.go +++ b/traefik.go @@ -127,16 +127,16 @@ type TraefikConfig struct { CertificatesResolvers CertificatesResolversStruct `yaml:"certificatesResolvers,omitempty"` } -func createTraefikFile(rootDomain string, useCloudflare bool, cloudflareEmail string, useTraefikHub bool) []byte { +func createTraefikFile() { entryPointsHttps := EntryPointHttpsStruct{ Address: ":443", Http: HttpsHttpStruct{ Tls: TlsStruct{ Domains: []HttpTlsDomainsStruct{ { - Main: "traefik." + rootDomain, + Main: "traefik." + configRootDomain, Sans: []string{ - "*." + rootDomain, + "*." + configRootDomain, }, }, }, @@ -145,7 +145,7 @@ func createTraefikFile(rootDomain string, useCloudflare bool, cloudflareEmail st } certificatesResolvers := CertificatesResolversStruct{} - if useCloudflare == true { + if configUseCloudflare == true { entryPointsHttpsCloudflare := EntryPointHttpsStruct{ Http: HttpsHttpStruct{ Tls: TlsStruct{ @@ -175,7 +175,7 @@ func createTraefikFile(rootDomain string, useCloudflare bool, cloudflareEmail st certificatesResolversCloudflare := CertificatesResolversStruct{ DnsCloudflare: CertDnsCloudflareStruct{ Acme: CertAcmeStruct{ - Email: cloudflareEmail, + Email: configCloudflareEmail, Storage: "/etc/traefik/acme.json", DnsChallenge: DnsChallengeStruct{ Provider: "cloudflare", @@ -205,7 +205,7 @@ func createTraefikFile(rootDomain string, useCloudflare bool, cloudflareEmail st }, }, Experimental: ExperimentalStruct{ - Hub: useTraefikHub, + Hub: configUseTraefikHub, }, Hub: HubStruct{ Tls: HubTlsStruct{ @@ -252,14 +252,20 @@ func createTraefikFile(rootDomain string, useCloudflare bool, cloudflareEmail st yamlData, err := yaml.Marshal(&traefikConfig) - //var b bytes.Buffer - //yamlEncoder := yaml.NewEncoder(&b) - //yamlEncoder.SetIndent(2) - //yamlEncoder.Encode(&yamlData) - if err != nil { fmt.Printf("Error while Marshaling. %v", err) } - return yamlData + writeFile("AppData/traefik-proxy", "acme.json", []byte(""), 0600) + writeFile("AppData/traefik-proxy", "traefik.yml", yamlData, 0644) + + if configUseCloudflare == true { + writeFile("secrets", "cf_email", []byte(configCloudflareEmail), 0600) + writeFile("secrets", "cf_api_key", []byte(configCloudflareApiKey), 0600) + } + if configIsLocalEnvironment == true { + traefikTlsDynamicConfig := createTraefikTlsFile() + + writeFile("AppData/traefik-proxy/rules", "local-tls.yml", traefikTlsDynamicConfig, 0644) + } }