mirror of
https://github.com/bytedream/docker4ssh.git
synced 2025-05-09 12:15:11 +02:00
202 lines
6.7 KiB
Go
202 lines
6.7 KiB
Go
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
|
|
}
|