Files
oneterm/backend/internal/sshsrv/handler.go

129 lines
3.9 KiB
Go

package sshsrv
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/gin-gonic/gin"
"github.com/gliderlabs/ssh"
"go.uber.org/zap"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/sync/errgroup"
"github.com/veops/oneterm/internal/acl"
"github.com/veops/oneterm/internal/model"
"github.com/veops/oneterm/internal/version"
"github.com/veops/oneterm/pkg/config"
"github.com/veops/oneterm/pkg/logger"
)
func handler(sess ssh.Session) {
defer acl.Logout(sess.Context().Value("session").(*acl.Session))
pty, _, isPty := sess.Pty()
if !isPty {
logger.L().Error("not a pty request")
return
}
ctx := &gin.Context{
Request: &http.Request{
RemoteAddr: sess.RemoteAddr().String(),
URL: &url.URL{
RawQuery: fmt.Sprintf("info=true&w=%d&h=%d", pty.Window.Width, pty.Window.Height),
},
},
}
ctx.Set("sessionType", model.SESSIONTYPE_CLIENT)
ctx.Set("session", sess.Context().Value("session"))
eg, gctx := errgroup.WithContext(sess.Context())
r, w := io.Pipe()
eg.Go(func() error {
_, err := io.Copy(w, sess)
return err
})
eg.Go(func() error {
defer sess.Close()
defer r.Close()
defer w.Close()
vw := initialView(ctx, sess, r, w, gctx)
defer vw.RecordHisCmd()
p := tea.NewProgram(vw, tea.WithContext(gctx), tea.WithInput(r), tea.WithOutput(sess))
_, err := p.Run()
return err
})
if err := eg.Wait(); err != nil {
logger.L().Debug("handler stopped", zap.Error(err))
}
}
func signer() ssh.Signer {
s, err := gossh.ParsePrivateKey([]byte(config.Cfg.Ssh.PrivateKey))
if err != nil {
logger.L().Fatal("failed parse signer", zap.Error(err))
}
return s
}
func banner() string {
// Professional blue theme gradient
gradient1 := lipgloss.NewStyle().Foreground(lipgloss.Color("#3F75FF")) // Bright primary
gradient2 := lipgloss.NewStyle().Foreground(lipgloss.Color("#2f54eb")) // Primary color
gradient3 := lipgloss.NewStyle().Foreground(lipgloss.Color("#7f97fa")) // Light primary
versionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#8c8c8c")).Italic(true)
bannerText := `
██████╗ ███╗ ██╗███████╗████████╗███████╗██████╗ ███╗ ███╗
██╔═══██╗████╗ ██║██╔════╝╚══██╔══╝██╔════╝██╔══██╗████╗ ████║
██║ ██║██╔██╗ ██║█████╗ ██║ █████╗ ██████╔╝██╔████╔██║
██║ ██║██║╚██╗██║██╔══╝ ██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║
╚██████╔╝██║ ╚████║███████╗ ██║ ███████╗██║ ██║██║ ╚═╝ ██║
╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝`
lines := strings.Split(bannerText, "\n")
var result strings.Builder
for i, line := range lines {
if line == "" {
result.WriteString("\n")
continue
}
var style lipgloss.Style
switch {
case i <= 2:
style = gradient1
case i <= 4:
style = gradient2
default:
style = gradient3
}
result.WriteString(style.Render(line))
result.WriteString("\n")
}
tagline := lipgloss.NewStyle().
Foreground(lipgloss.Color("#2f54eb")).
Bold(true).
PaddingLeft(15).
Render("✨ Enterprise Bastion Host Solution")
versionText := versionStyle.
PaddingLeft(25).
Render(version.Version)
result.WriteString("\n")
result.WriteString(tagline)
result.WriteString(" ")
result.WriteString(versionText)
result.WriteString("\n")
return result.String()
}