package main import ( "fmt" "os" "os/exec" "strings" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/authelia/authelia/v4/internal/utils" ) // HostEntry represents an entry in /etc/hosts. type HostEntry struct { Domain string IP string } var hostEntries = []HostEntry{ // For authelia backend. {Domain: "authelia.example.com", IP: "192.168.240.50"}, // For common tests. {Domain: "login.example.com", IP: "192.168.240.100"}, {Domain: "admin.example.com", IP: "192.168.240.100"}, {Domain: "singlefactor.example.com", IP: "192.168.240.100"}, {Domain: "dev.example.com", IP: "192.168.240.100"}, {Domain: "home.example.com", IP: "192.168.240.100"}, {Domain: "mx1.mail.example.com", IP: "192.168.240.100"}, {Domain: "mx2.mail.example.com", IP: "192.168.240.100"}, {Domain: "public.example.com", IP: "192.168.240.100"}, {Domain: "secure.example.com", IP: "192.168.240.100"}, {Domain: "mail.example.com", IP: "192.168.240.100"}, {Domain: "duo.example.com", IP: "192.168.240.100"}, // For Traefik suite. {Domain: "traefik.example.com", IP: "192.168.240.100"}, // For HAProxy suite. {Domain: "haproxy.example.com", IP: "192.168.240.100"}, // For testing network ACLs. {Domain: "proxy-client1.example.com", IP: "192.168.240.201"}, {Domain: "proxy-client2.example.com", IP: "192.168.240.202"}, {Domain: "proxy-client3.example.com", IP: "192.168.240.203"}, // Redis Replicas {Domain: "redis-node-0.example.com", IP: "192.168.240.110"}, {Domain: "redis-node-1.example.com", IP: "192.168.240.111"}, {Domain: "redis-node-2.example.com", IP: "192.168.240.112"}, // Redis Sentinel Replicas {Domain: "redis-sentinel-0.example.com", IP: "192.168.240.120"}, {Domain: "redis-sentinel-1.example.com", IP: "192.168.240.121"}, {Domain: "redis-sentinel-2.example.com", IP: "192.168.240.122"}, // Kubernetes dashboard. {Domain: "kubernetes.example.com", IP: "192.168.240.110"}, // OIDC tester app {Domain: "oidc.example.com", IP: "192.168.240.100"}, {Domain: "oidc-public.example.com", IP: "192.168.240.100"}, } func runCommand(cmd string, args ...string) { command := utils.CommandWithStdout(cmd, args...) err := command.Run() if err != nil { panic(err) } } func checkCommandExist(cmd string, resolutionHint string) { fmt.Print("Checking if '" + cmd + "' command is installed...") command := exec.Command("bash", "-c", "command -v "+cmd) //nolint:gosec // Used only in development. err := command.Run() if err != nil { msg := "[ERROR] You must install " + cmd + " on your machine." if resolutionHint != "" { msg += fmt.Sprintf(" %s", resolutionHint) } log.Fatal(msg) } fmt.Println(" OK") } func createTemporaryDirectory() { err := os.MkdirAll("/tmp/authelia", 0755) if err != nil { panic(err) } } func bootstrapPrintln(args ...interface{}) { a := make([]interface{}, 0) a = append(a, "[BOOTSTRAP]") a = append(a, args...) fmt.Println(a...) } func shell(cmd string) { runCommand("bash", "-c", cmd) } func prepareHostsFile() { contentBytes, err := readHostsFile() if err != nil { panic(err) } lines := strings.Split(string(contentBytes), "\n") toBeAddedLine := make([]string, 0) modified := false for _, entry := range hostEntries { domainInHostFile := false for i, line := range lines { domainFound := strings.Contains(line, entry.Domain) ipFound := strings.Contains(line, entry.IP) if domainFound { domainInHostFile = true // The IP is not up to date. if ipFound { break } else { lines[i] = entry.IP + " " + entry.Domain modified = true break } } } if !domainInHostFile { toBeAddedLine = append(toBeAddedLine, entry.IP+" "+entry.Domain) } } if len(toBeAddedLine) > 0 { lines = append(lines, toBeAddedLine...) modified = true } fd, err := os.CreateTemp("/tmp/authelia/", "hosts") if err != nil { panic(err) } _, err = fd.Write([]byte(strings.Join(lines, "\n"))) if err != nil { panic(err) } if modified { bootstrapPrintln("/etc/hosts needs to be updated") shell(fmt.Sprintf("cat %s | sudo tee /etc/hosts > /dev/null", fd.Name())) } err = fd.Close() if err != nil { panic(err) } } // ReadHostsFile reads the hosts file. func readHostsFile() ([]byte, error) { bs, err := os.ReadFile("/etc/hosts") if err != nil { return nil, err } return bs, nil } func readVersion(cmd string, args ...string) { command := exec.Command(cmd, args...) b, err := command.Output() if err != nil { panic(err) } fmt.Print(cmd + " => " + string(b)) } func readVersions() { readVersion("go", "version") readVersion("node", "--version") readVersion("pnpm", "--version") readVersion("docker", "--version") readVersion("docker-compose", "version") } // Bootstrap bootstrap authelia dev environment. func Bootstrap(cobraCmd *cobra.Command, args []string) { bootstrapPrintln("Checking command installation...") checkCommandExist("node", "Follow installation guidelines from https://nodejs.org/en/download/package-manager/ or download installer from https://nodejs.org/en/download/") checkCommandExist("pnpm", "Follow installation guidelines from https://pnpm.io/installation") checkCommandExist("docker", "Follow installation guidelines from https://docs.docker.com/get-docker/") checkCommandExist("docker-compose", "Follow installation guidelines from https://docs.docker.com/compose/install/") bootstrapPrintln("Getting versions of tools") readVersions() bootstrapPrintln("Checking if GOPATH is set") goPathFound := false for _, v := range os.Environ() { if strings.HasPrefix(v, "GOPATH=") { goPathFound = true break } } if !goPathFound { log.Fatal("GOPATH is not set") } createTemporaryDirectory() bootstrapPrintln("Preparing /etc/hosts to serve subdomains of example.com...") prepareHostsFile() fmt.Println() bootstrapPrintln("Run 'authelia-scripts suites setup Standalone' to start Authelia and visit https://home.example.com:8080.") bootstrapPrintln("More details at https://github.com/authelia/authelia/blob/master/docs/getting-started.md") }