package ssh import ( "bytes" "context" "database/sql" "docker4ssh/database" "docker4ssh/docker" "docker4ssh/utils" "fmt" "go.uber.org/zap" "strconv" "sync" "time" ) var ( allContainers []*docker.InteractiveContainer ) func closeAllContainers(ctx context.Context) { var wg sync.WaitGroup for _, container := range allContainers { wg.Add(1) container := container go func() { container.Stop(ctx) wg.Done() }() } wg.Wait() } func connection(client *docker.Client, user *User) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() container, ok := getContainer(ctx, client, user) if !ok { zap.S().Errorf("Failed to create container for %s", user.ID) return } user.Container = container.SimpleContainer var found bool for _, cont := range allContainers { if cont == container { found = true } } if !found { allContainers = append(allContainers, container) } // check if the container is running and start it if not if running, err := container.Running(ctx); err == nil && !running { if err = container.Start(ctx); err != nil { zap.S().Errorf("Failed to start container %s: %v", container.ContainerID, err) fmt.Fprintln(user.Terminal, "Failed to start container") return } zap.S().Infof("Started container %s with internal id '%s', ip '%s'", container.ContainerID, container.ContainerID, container.Network.IP) } else if err != nil { zap.S().Errorf("Failed to get container running state: %v", err) fmt.Fprintln(user.Terminal, "Failed to check container running state") } config := container.Config() if user.Profile.StartupInformation { buf := &bytes.Buffer{} fmt.Fprintf(buf, "┌───Container────────────────┐\r\n") fmt.Fprintf(buf, "│ Container ID: %-12s │\r\n", container.ContainerID) fmt.Fprintf(buf, "│ Network Mode: %-12s │\r\n", config.NetworkMode.Name()) fmt.Fprintf(buf, "│ Configurable: %-12t │\r\n", config.Configurable) fmt.Fprintf(buf, "│ Run Level: %-12s │\r\n", config.RunLevel.Name()) fmt.Fprintf(buf, "│ Exit After: %-12s │\r\n", config.ExitAfter) fmt.Fprintf(buf, "│ Keep On Exit: %-12t │\r\n", config.KeepOnExit) fmt.Fprintf(buf, "└──────────────Information───┘\r\n") user.Terminal.Write(buf.Bytes()) } // start a new terminal session if err := container.Terminal(ctx, user.Terminal); err != nil { zap.S().Errorf("Failed to serve %s terminal: %v", container.ContainerID, err) fmt.Fprintln(user.Terminal, "Failed to serve terminal") } if config.RunLevel == docker.User && container.TerminalCount() == 0 { if err := container.Stop(ctx); err != nil { zap.S().Errorf("Error occoured while stopping container %s: %v", container.ContainerID, err) } else { lenBefore := len(allContainers) for i, cont := range allContainers { if cont == container { allContainers[i] = allContainers[lenBefore-1] allContainers = allContainers[:lenBefore-1] break } } if lenBefore == len(allContainers) { zap.S().Warnf("Stopped container %s, but failed to remove it from the global container scope", container.ContainerID) } else { zap.S().Infof("Stopped container %s", container.ContainerID) } } } zap.S().Infof("Stopped session for user %s", user.ID) } func getContainer(ctx context.Context, client *docker.Client, user *User) (container *docker.InteractiveContainer, ok bool) { db := database.GetDatabase() var config docker.Config // check if the user has a container (id) assigned if user.Profile.ContainerID != "" { for _, cont := range allContainers { if cont.FullContainerID == user.Profile.ContainerID { return cont, true } } settings, err := db.SettingsByContainerID(user.Profile.ContainerID) if err != nil { zap.S().Errorf("Failed to get stored container config for container %s: %v", user.Profile.ContainerID, err) fmt.Fprintf(user.Terminal, "Could not connect to saved container") return nil, false } config = docker.Config{ NetworkMode: docker.NetworkMode(*settings.NetworkMode), Configurable: *settings.Configurable, RunLevel: docker.RunLevel(*settings.RunLevel), StartupInformation: *settings.StartupInformation, ExitAfter: *settings.ExitAfter, KeepOnExit: *settings.KeepOnExit, } container, err = docker.InteractiveContainerFromID(ctx, client, config, user.Profile.ContainerID) if err != nil { zap.S().Errorf("Failed to get container from id %s: %v", user.Profile.ContainerID, err) fmt.Fprintf(user.Terminal, "Failed to get container") return nil, false } zap.S().Infof("Re-used container %s for user %s", user.Profile.ContainerID, user.ID) } else { config = docker.Config{ NetworkMode: docker.NetworkMode(user.Profile.NetworkMode), Configurable: user.Profile.Configurable, RunLevel: docker.RunLevel(user.Profile.RunLevel), StartupInformation: user.Profile.StartupInformation, ExitAfter: user.Profile.ExitAfter, KeepOnExit: user.Profile.KeepOnExit, } image, out, err := docker.NewImage(ctx, client.Client, user.Profile.Image) if err != nil { zap.S().Errorf("Failed to get '%s' image for profile %s: %v", user.Profile.Image, user.Profile.Name(), err) fmt.Fprintf(user.Terminal, "Failed to get image %s", image.Ref()) return nil, false } if out != nil { if err := utils.DisplayJSONMessagesStream(out, user.Terminal, user.Terminal); err != nil { zap.S().Fatalf("Failed to fetch '%s' docker image: %v", image.Ref(), err) fmt.Fprintf(user.Terminal, "Failed to fetch image %s", image.Ref()) return nil, false } } container, err = docker.NewInteractiveContainer(ctx, client, config, image, strconv.Itoa(int(time.Now().Unix()))) if err != nil { zap.S().Errorf("Failed to create interactive container: %v", err) fmt.Fprintln(user.Terminal, "Failed to create interactive container") return nil, false } zap.S().Infof("Created new %s container (%s) for user %s", image.Ref(), container.ContainerID, user.ID) } if _, err := db.SettingsByContainerID(container.FullContainerID); err != nil { if err == sql.ErrNoRows { rawNetworkMode := int(config.NetworkMode) rawRunLevel := int(config.RunLevel) if err := db.SetSettings(container.FullContainerID, database.Settings{ NetworkMode: &rawNetworkMode, Configurable: &config.Configurable, RunLevel: &rawRunLevel, StartupInformation: &config.StartupInformation, ExitAfter: &config.ExitAfter, KeepOnExit: &config.KeepOnExit, }); err != nil { zap.S().Errorf("Failed to update settings for container %s for user %s: %v", container.ContainerID, user.ID, err) return nil, false } } } return container, true }