package main import ( "converge/pkg/comms" "converge/pkg/models" "converge/pkg/server/converge" "converge/pkg/support/websocketutil" "fmt" "log" "math/rand" "net" "net/http" _ "net/http/pprof" "os" "regexp" "strconv" "strings" _ "time/tzdata" ) func parsePublicId(path string) (publicId string, _ error) { pattern := regexp.MustCompile("^/[^/]+/([^/]+)$") matches := pattern.FindStringSubmatch(path) if len(matches) != 2 { return "", fmt.Errorf("Invalid URL path '%s'", path) } return matches[1], nil } func catchAllHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/docs", http.StatusFound) return } func printHelp(msg string) { if msg != "" { fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", msg) } helpText := "Usage: converge [options]\n" + "\n" + "Converge server is a rendez-vous server for debugging continuous integration\n" + "jobs be providing the capability to log into the agents where jobs are running.\n" + "This is achieve by starting an agent in the continuous integration job\n" + "which connects to Converge using a websocket connection. The end user also connects\n" + "to Converge using ssh over websockets. The server then matches the end-user with\n" + "the agent running in the continous integration job (the rendez-vous) and sets up\n" + "an end-to-end SSH connection between end-user and agent, with the agent providing\n" + "an embedded SSH server to provide interactive access to the end-user. This works\n" + "both on linux and on windows.\n" + "\n" + "-d : directory where static content of converge is placed" fmt.Fprintln(os.Stderr, helpText) os.Exit(1) } func main() { downloadDir := "../static" args := os.Args[1:] for len(args) > 0 && strings.HasPrefix(args[0], "-") { switch args[0] { case "-d": if len(args) <= 1 { printHelp("The -d option expects an argument") } downloadDir = args[1] args = args[1:] default: printHelp("Unknown option " + args[0]) } args = args[1:] } log.Println("Content directory", downloadDir) if len(args) != 0 { printHelp("") } userPassword := comms.UserPassword{ Username: strconv.Itoa(rand.Int()), Password: strconv.Itoa(rand.Int()), } username, ok := os.LookupEnv("CONVERGE_USERNAME") if ok { userPassword.Username = username } else { os.Setenv("CONVERGE_USERNAME", userPassword.Username) } password, ok := os.LookupEnv("CONVERGE_PASSWORD") if ok { userPassword.Password = password } else { os.Setenv("CONVERGE_PASSWORD", userPassword.Password) } log.Printf("Using username '%s' and password '%s'", userPassword.Username, userPassword.Password) notifications := make(chan *models.State, 10) admin := converge.NewAdmin(notifications) websessions := converge.NewWebSessions(notifications) // For agents connecting registrationService := websocketutil.WebSocketService{ Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) { publicId, err := parsePublicId(r.URL.Path) if err != nil { log.Printf("Cannot parse public id from url: '%v'\n", err) return } log.Printf("Got registration connection: '%s'\n", publicId) err = admin.Register(publicId, conn, userPassword) if err != nil { log.Printf("Error %v\n", err) } }, } // For users connecting with ssh clientService := websocketutil.WebSocketService{ Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) { publicId, err := parsePublicId(r.URL.Path) if err != nil { log.Printf("Cannot parse public id from url: '%v'\n", err) return } log.Printf("Got client connection: '%s'\n", publicId) err = admin.Connect(publicId, conn) if err != nil { log.Printf("Error %v\n", err) } }, } // for the web browser getting live status updates. sessionService := websocketutil.WebSocketService{ Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) { websession := websessions.NewSession(conn) defer websessions.SessionClosed(websession) location, err := converge.GetUserLocation(r) if err != nil { panic(err) } websession.WriteNotifications(location) }, Text: true, } // websocket endpoints http.HandleFunc("/agent/", registrationService.Handle) http.HandleFunc("/client/", clientService.Handle) http.HandleFunc("/ws/sessions", sessionService.Handle) // create filehandler with templating for html files. http.Handle("/docs/", http.StripPrefix("/docs/", http.HandlerFunc(pageHandler))) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(downloadDir)))) http.HandleFunc("/", catchAllHandler) // Start HTTP server fmt.Println("Rendez-vous server listening on :8000") log.Fatal(http.ListenAndServe(":8000", nil)) }