style(be):拆分模块目录

This commit is contained in:
neverteaser
2021-03-20 21:42:32 +08:00
committed by dushixiang
parent d21b8cca60
commit 8e7bd69312
36 changed files with 68 additions and 48 deletions

View File

@ -4,9 +4,9 @@ import (
"strings"
"time"
"next-terminal/server/global"
"next-terminal/pkg/global"
"next-terminal/pkg/totp"
"next-terminal/server/model"
"next-terminal/server/totp"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
@ -40,6 +40,11 @@ type Authorization struct {
User model.User
}
//
//type UserServer struct {
// repository.UserRepository
//}
func LoginEndpoint(c echo.Context) error {
var loginAccount LoginAccount
if err := c.Bind(&loginAccount); err != nil {

View File

@ -1,8 +1,8 @@
package api
import (
"next-terminal/server/constant"
"next-terminal/server/global"
"next-terminal/pkg/constant"
"next-terminal/pkg/global"
"next-terminal/server/model"
"github.com/labstack/echo/v4"

View File

@ -8,7 +8,7 @@ import (
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/pkg/constant"
"next-terminal/server/model"
"next-terminal/server/utils"

View File

@ -5,7 +5,7 @@ import (
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/pkg/constant"
"next-terminal/server/model"
"next-terminal/server/utils"

View File

@ -4,8 +4,8 @@ import (
"strconv"
"strings"
"next-terminal/pkg/global"
"next-terminal/pkg/log"
"next-terminal/server/global"
"github.com/labstack/echo/v4"
)

View File

@ -7,8 +7,8 @@ import (
"strings"
"time"
"next-terminal/server/constant"
"next-terminal/server/global"
"next-terminal/pkg/constant"
"next-terminal/pkg/global"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"

View File

@ -4,13 +4,14 @@ import (
"github.com/labstack/echo/v4"
)
//
// todo 监控
func MonitorEndpoint(c echo.Context) (err error) {
//ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
//if err != nil {
// log.Errorf("升级为WebSocket协议失败%v", err.Error())
// return err
//}
return
}

View File

@ -1,7 +1,7 @@
package api
import (
"next-terminal/server/constant"
"next-terminal/pkg/constant"
"next-terminal/server/repository"
"github.com/labstack/echo/v4"

View File

@ -7,11 +7,11 @@ import (
"strings"
"time"
"next-terminal/pkg/global"
"next-terminal/pkg/log"
"next-terminal/server/global"
"next-terminal/pkg/service"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"

View File

@ -4,7 +4,7 @@ import (
"strconv"
"strings"
"next-terminal/server/global"
"next-terminal/pkg/global"
"next-terminal/server/model"
"next-terminal/server/utils"

View File

@ -13,9 +13,9 @@ import (
"strings"
"sync"
"next-terminal/pkg/constant"
"next-terminal/pkg/global"
"next-terminal/pkg/log"
"next-terminal/server/constant"
"next-terminal/server/global"
"next-terminal/server/model"
"next-terminal/server/utils"

View File

@ -7,12 +7,12 @@ import (
"strconv"
"time"
"next-terminal/pkg/constant"
"next-terminal/pkg/global"
"next-terminal/pkg/guacd"
"next-terminal/pkg/log"
"next-terminal/server/constant"
"next-terminal/server/global"
"next-terminal/pkg/term"
"next-terminal/server/model"
"next-terminal/server/term"
"next-terminal/server/utils"
"github.com/gorilla/websocket"

View File

@ -1,63 +0,0 @@
package api
import (
"strconv"
"time"
"next-terminal/pkg/log"
"next-terminal/server/constant"
)
func SetupTicker() {
// 每隔一小时删除一次未使用的会话信息
unUsedSessionTicker := time.NewTicker(time.Minute * 60)
go func() {
for range unUsedSessionTicker.C {
sessions, _ := sessionRepository.FindByStatusIn([]string{constant.NoConnect, constant.Connecting})
if len(sessions) > 0 {
now := time.Now()
for i := range sessions {
if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 {
_ = sessionRepository.DeleteById(sessions[i].ID)
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
log.Infof("会话「%v」ID「%v」超过1小时未打开已删除。", s, sessions[i].ID)
}
}
}
}
}()
// 每日凌晨删除超过时长限制的会话
timeoutSessionTicker := time.NewTicker(time.Hour * 24)
go func() {
for range timeoutSessionTicker.C {
property, err := propertyRepository.FindByName("session-saved-limit")
if err != nil {
return
}
if property.Value == "" || property.Value == "-" {
return
}
limit, err := strconv.Atoi(property.Value)
if err != nil {
return
}
sessions, err := sessionRepository.FindOutTimeSessions(limit)
if err != nil {
return
}
if len(sessions) > 0 {
var sessionIds []string
for i := range sessions {
sessionIds = append(sessionIds, sessions[i].ID)
}
err := sessionRepository.DeleteByIds(sessionIds)
if err != nil {
log.Errorf("删除离线会话失败 %v", err)
}
}
}
}()
}

View File

@ -5,10 +5,10 @@ import (
"path"
"strconv"
"next-terminal/pkg/constant"
"next-terminal/pkg/global"
"next-terminal/pkg/guacd"
"next-terminal/pkg/log"
"next-terminal/server/constant"
"next-terminal/server/global"
"next-terminal/server/model"
"github.com/gorilla/websocket"

View File

@ -4,8 +4,8 @@ import (
"strconv"
"strings"
"next-terminal/pkg/global"
"next-terminal/pkg/log"
"next-terminal/server/global"
"next-terminal/server/model"
"next-terminal/server/utils"

View File

@ -1,89 +0,0 @@
package config
import (
"strings"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
type Config struct {
Debug bool
Demo bool
DB string
Server *Server
Mysql *Mysql
Sqlite *Sqlite
ResetPassword string
}
type Mysql struct {
Hostname string
Port int
Username string
Password string
Database string
}
type Sqlite struct {
File string
}
type Server struct {
Addr string
Cert string
Key string
}
func SetupConfig() *Config {
viper.SetConfigName("config")
viper.SetConfigType("yml")
viper.AddConfigPath("/etc/next-terminal/")
viper.AddConfigPath("$HOME/.next-terminal")
viper.AddConfigPath(".")
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
pflag.String("db", "sqlite", "db mode")
pflag.String("sqlite.file", "next-terminal.db", "sqlite db file")
pflag.String("mysql.hostname", "127.0.0.1", "mysql hostname")
pflag.Int("mysql.port", 3306, "mysql port")
pflag.String("mysql.username", "mysql", "mysql username")
pflag.String("mysql.password", "mysql", "mysql password")
pflag.String("mysql.database", "next_terminal", "mysql database")
pflag.String("server.addr", "", "server listen addr")
pflag.String("server.cert", "", "tls cert file")
pflag.String("server.key", "", "tls key file")
pflag.String("reset-password", "", "")
pflag.Parse()
_ = viper.BindPFlags(pflag.CommandLine)
_ = viper.ReadInConfig()
var config = &Config{
DB: viper.GetString("db"),
Mysql: &Mysql{
Hostname: viper.GetString("mysql.hostname"),
Port: viper.GetInt("mysql.port"),
Username: viper.GetString("mysql.username"),
Password: viper.GetString("mysql.password"),
Database: viper.GetString("mysql.database"),
},
Sqlite: &Sqlite{
File: viper.GetString("sqlite.file"),
},
Server: &Server{
Addr: viper.GetString("server.addr"),
Cert: viper.GetString("server.cert"),
Key: viper.GetString("server.key"),
},
ResetPassword: viper.GetString("reset-password"),
Debug: viper.GetBool("debug"),
Demo: viper.GetBool("demo"),
}
return config
}

View File

@ -1,41 +0,0 @@
package constant
import "next-terminal/pkg/guacd"
const (
AccessRuleAllow = "allow" // 允许访问
AccessRuleReject = "reject" // 拒绝访问
Custom = "custom" // 密码
PrivateKey = "private-key" // 密钥
JobStatusRunning = "running" // 计划任务运行状态
JobStatusNotRunning = "not-running" // 计划任务未运行状态
FuncCheckAssetStatusJob = "check-asset-status-job" // 检测资产是否在线
FuncShellJob = "shell-job" // 执行Shell脚本
JobModeAll = "all" // 全部资产
JobModeCustom = "custom" // 自定义选择资产
SshMode = "ssh-mode" // ssh模式
MailHost = "mail-host" // 邮件服务器地址
MailPort = "mail-port" // 邮件服务器端口
MailUsername = "mail-username" // 邮件服务账号
MailPassword = "mail-password" // 邮件服务密码
NoConnect = "no_connect" // 会话状态:未连接
Connecting = "connecting" // 会话状态:连接中
Connected = "connected" // 会话状态:已连接
Disconnected = "disconnected" // 会话状态:已断开连接
Guacd = "guacd" // 接入模式guacd
Naive = "naive" // 接入模式:原生
TypeUser = "user" // 普通用户
TypeAdmin = "admin" // 管理员
)
var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode}
var RDPParameterNames = []string{guacd.Domain, guacd.RemoteApp, guacd.RemoteAppDir, guacd.RemoteAppArgs}
var VNCParameterNames = []string{guacd.ColorDepth, guacd.Cursor, guacd.SwapRedBlue, guacd.DestHost, guacd.DestPort}
var TelnetParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.UsernameRegex, guacd.PasswordRegex, guacd.LoginSuccessRegex, guacd.LoginFailureRegex}
var KubernetesParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.Namespace, guacd.Pod, guacd.Container, guacd.UesSSL, guacd.ClientCert, guacd.ClientKey, guacd.CaCert, guacd.IgnoreCert}

View File

@ -1,23 +0,0 @@
package global
import (
"next-terminal/server/config"
"github.com/patrickmn/go-cache"
"github.com/robfig/cron/v3"
)
var Cache *cache.Cache
var Config *config.Config
var Store *TunStore
var Cron *cron.Cron
type Security struct {
Rule string
IP string
}
var Securities []*Security

View File

@ -1,71 +0,0 @@
package global
import (
"strconv"
"sync"
"next-terminal/pkg/guacd"
"next-terminal/server/term"
"github.com/gorilla/websocket"
)
type Tun struct {
Protocol string
Mode string
WebSocket *websocket.Conn
Tunnel *guacd.Tunnel
NextTerminal *term.NextTerminal
}
func (r *Tun) Close(code int, reason string) {
if r.Tunnel != nil {
_ = r.Tunnel.Close()
}
if r.NextTerminal != nil {
_ = r.NextTerminal.Close()
}
ws := r.WebSocket
if ws != nil {
if r.Mode == "guacd" {
err := guacd.NewInstruction("error", "", strconv.Itoa(code))
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.String()))
disconnect := guacd.NewInstruction("disconnect")
_ = ws.WriteMessage(websocket.TextMessage, []byte(disconnect.String()))
} else {
msg := `{"type":"closed","content":"` + reason + `"}`
_ = ws.WriteMessage(websocket.TextMessage, []byte(msg))
}
}
}
type Observable struct {
Subject *Tun
Observers []Tun
}
type TunStore struct {
m sync.Map
}
func (s *TunStore) Set(k string, v *Observable) {
s.m.Store(k, v)
}
func (s *TunStore) Del(k string) {
s.m.Delete(k)
}
func (s *TunStore) Get(k string) (item *Observable, ok bool) {
value, ok := s.m.Load(k)
if ok {
return value.(*Observable), true
}
return item, false
}
func NewStore() *TunStore {
store := TunStore{sync.Map{}}
return &store
}

View File

@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"next-terminal/server/constant"
"next-terminal/server/global"
"next-terminal/pkg/constant"
"next-terminal/pkg/global"
"next-terminal/server/model"
"next-terminal/server/utils"

View File

@ -1,7 +1,7 @@
package repository
import (
"next-terminal/server/constant"
"next-terminal/pkg/constant"
"next-terminal/server/model"
"gorm.io/gorm"

View File

@ -1,7 +1,7 @@
package repository
import (
"next-terminal/server/constant"
"next-terminal/pkg/constant"
"next-terminal/server/model"
"gorm.io/gorm"

View File

@ -5,7 +5,7 @@ import (
"path"
"time"
"next-terminal/server/constant"
"next-terminal/pkg/constant"
"next-terminal/server/model"
"gorm.io/gorm"

View File

@ -1,320 +0,0 @@
package service
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"next-terminal/pkg/log"
"next-terminal/server/constant"
"next-terminal/server/global"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/term"
"next-terminal/server/utils"
"github.com/robfig/cron/v3"
)
type JobService struct {
jobRepository *repository.JobRepository
jobLogRepository *repository.JobLogRepository
assetRepository *repository.AssetRepository
credentialRepository *repository.CredentialRepository
}
func NewJobService(jobRepository *repository.JobRepository, jobLogRepository *repository.JobLogRepository, assetRepository *repository.AssetRepository, credentialRepository *repository.CredentialRepository) *JobService {
return &JobService{jobRepository: jobRepository, jobLogRepository: jobLogRepository, assetRepository: assetRepository, credentialRepository: credentialRepository}
}
func (r JobService) ChangeStatusById(id, status string) error {
job, err := r.jobRepository.FindById(id)
if err != nil {
return err
}
if status == constant.JobStatusRunning {
j, err := getJob(&job, &r)
if err != nil {
return err
}
entryID, err := global.Cron.AddJob(job.Cron, j)
if err != nil {
return err
}
log.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries()))
jobForUpdate := model.Job{ID: id, Status: constant.JobStatusRunning, CronJobId: int(entryID)}
return r.jobRepository.UpdateById(&jobForUpdate)
} else {
global.Cron.Remove(cron.EntryID(job.CronJobId))
log.Debugf("关闭计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries()))
jobForUpdate := model.Job{ID: id, Status: constant.JobStatusNotRunning}
return r.jobRepository.UpdateById(&jobForUpdate)
}
}
func getJob(j *model.Job, jobService *JobService) (job cron.Job, err error) {
switch j.Func {
case constant.FuncCheckAssetStatusJob:
job = CheckAssetStatusJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata, jobService: jobService}
case constant.FuncShellJob:
job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata, jobService: jobService}
default:
return nil, errors.New("未识别的任务")
}
return job, err
}
type CheckAssetStatusJob struct {
ID string
Mode string
ResourceIds string
Metadata string
jobService *JobService
}
func (r CheckAssetStatusJob) Run() {
if r.ID == "" {
return
}
var assets []model.Asset
if r.Mode == constant.JobModeAll {
assets, _ = r.jobService.assetRepository.FindAll()
} else {
assets, _ = r.jobService.assetRepository.FindByIds(strings.Split(r.ResourceIds, ","))
}
if len(assets) == 0 {
return
}
msgChan := make(chan string)
for i := range assets {
asset := assets[i]
go func() {
t1 := time.Now()
active := utils.Tcping(asset.IP, asset.Port)
elapsed := time.Since(t1)
msg := fmt.Sprintf("资产「%v」存活状态检测完成存活「%v」耗时「%v」", asset.Name, active, elapsed)
_ = r.jobService.assetRepository.UpdateActiveById(active, asset.ID)
log.Infof(msg)
msgChan <- msg
}()
}
var message = ""
for i := 0; i < len(assets); i++ {
message += <-msgChan + "\n"
}
_ = r.jobService.jobRepository.UpdateLastUpdatedById(r.ID)
jobLog := model.JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: utils.NowJsonTime(),
Message: message,
}
_ = r.jobService.jobLogRepository.Create(&jobLog)
}
type ShellJob struct {
ID string
Mode string
ResourceIds string
Metadata string
jobService *JobService
}
type MetadataShell struct {
Shell string
}
func (r ShellJob) Run() {
if r.ID == "" {
return
}
var assets []model.Asset
if r.Mode == constant.JobModeAll {
assets, _ = r.jobService.assetRepository.FindByProtocol("ssh")
} else {
assets, _ = r.jobService.assetRepository.FindByProtocolAndIds("ssh", strings.Split(r.ResourceIds, ","))
}
if len(assets) == 0 {
return
}
var metadataShell MetadataShell
err := json.Unmarshal([]byte(r.Metadata), &metadataShell)
if err != nil {
log.Errorf("JSON数据解析失败 %v", err)
return
}
msgChan := make(chan string)
for i := range assets {
asset, err := r.jobService.assetRepository.FindById(assets[i].ID)
if err != nil {
msgChan <- fmt.Sprintf("资产「%v」Shell执行失败查询数据异常「%v」", assets[i].Name, err.Error())
return
}
var (
username = asset.Username
password = asset.Password
privateKey = asset.PrivateKey
passphrase = asset.Passphrase
ip = asset.IP
port = asset.Port
)
if asset.AccountType == "credential" {
credential, err := r.jobService.credentialRepository.FindById(asset.CredentialId)
if err != nil {
msgChan <- fmt.Sprintf("资产「%v」Shell执行失败查询授权凭证数据异常「%v」", assets[i].Name, err.Error())
return
}
if credential.Type == constant.Custom {
username = credential.Username
password = credential.Password
} else {
username = credential.Username
privateKey = credential.PrivateKey
passphrase = credential.Passphrase
}
}
go func() {
t1 := time.Now()
result, err := ExecCommandBySSH(metadataShell.Shell, ip, port, username, password, privateKey, passphrase)
elapsed := time.Since(t1)
var msg string
if err != nil {
msg = fmt.Sprintf("资产「%v」Shell执行失败返回值「%v」耗时「%v」", asset.Name, err.Error(), elapsed)
log.Infof(msg)
} else {
msg = fmt.Sprintf("资产「%v」Shell执行成功返回值「%v」耗时「%v」", asset.Name, result, elapsed)
log.Infof(msg)
}
msgChan <- msg
}()
}
var message = ""
for i := 0; i < len(assets); i++ {
message += <-msgChan + "\n"
}
_ = r.jobService.jobRepository.UpdateLastUpdatedById(r.ID)
jobLog := model.JobLog{
ID: utils.UUID(),
JobId: r.ID,
Timestamp: utils.NowJsonTime(),
Message: message,
}
_ = r.jobService.jobLogRepository.Create(&jobLog)
}
func ExecCommandBySSH(cmd, ip string, port int, username, password, privateKey, passphrase string) (result string, err error) {
sshClient, err := term.NewSshClient(ip, port, username, password, privateKey, passphrase)
if err != nil {
return "", err
}
session, err := sshClient.NewSession()
if err != nil {
return "", err
}
defer session.Close()
//执行远程命令
combo, err := session.CombinedOutput(cmd)
if err != nil {
return "", err
}
return string(combo), nil
}
func (r JobService) ExecJobById(id string) (err error) {
job, err := r.jobRepository.FindById(id)
if err != nil {
return err
}
j, err := getJob(&job, &r)
if err != nil {
return err
}
j.Run()
return nil
}
func (r JobService) InitJob() error {
jobs, _ := r.jobRepository.FindByFunc(constant.FuncCheckAssetStatusJob)
if jobs == nil {
job := model.Job{
ID: utils.UUID(),
Name: "资产状态检测",
Func: constant.FuncCheckAssetStatusJob,
Cron: "0 0 0/1 * * ?",
Mode: constant.JobModeAll,
Status: constant.JobStatusRunning,
Created: utils.NowJsonTime(),
Updated: utils.NowJsonTime(),
}
if err := r.jobRepository.Create(&job); err != nil {
return err
}
log.Debugf("创建计划任务「%v」cron「%v」", job.Name, job.Cron)
} else {
for i := range jobs {
if jobs[i].Status == constant.JobStatusRunning {
err := r.ChangeStatusById(jobs[i].ID, constant.JobStatusRunning)
if err != nil {
return err
}
log.Debugf("启动计划任务「%v」cron「%v」", jobs[i].Name, jobs[i].Cron)
}
}
}
return nil
}
func (r JobService) Create(o *model.Job) (err error) {
if o.Status == constant.JobStatusRunning {
j, err := getJob(o, &r)
if err != nil {
return err
}
jobId, err := global.Cron.AddJob(o.Cron, j)
if err != nil {
return err
}
o.CronJobId = int(jobId)
}
return r.jobRepository.Create(o)
}
func (r JobService) DeleteJobById(id string) error {
job, err := r.jobRepository.FindById(id)
if err != nil {
return err
}
if job.Status == constant.JobStatusRunning {
if err := r.ChangeStatusById(id, constant.JobStatusNotRunning); err != nil {
return err
}
}
return r.jobRepository.DeleteJobById(id)
}

View File

@ -1,42 +0,0 @@
package service
import (
"net/smtp"
"next-terminal/pkg/log"
"next-terminal/server/constant"
"next-terminal/server/repository"
"github.com/jordan-wright/email"
)
type MailService struct {
propertyRepository *repository.PropertyRepository
}
func NewMailService(propertyRepository *repository.PropertyRepository) *MailService {
return &MailService{propertyRepository: propertyRepository}
}
func (r MailService) SendMail(to, subject, text string) {
propertiesMap := r.propertyRepository.FindAllMap()
host := propertiesMap[constant.MailHost]
port := propertiesMap[constant.MailPort]
username := propertiesMap[constant.MailUsername]
password := propertiesMap[constant.MailPassword]
if host == "" || port == "" || username == "" || password == "" {
log.Debugf("邮箱信息不完整,跳过发送邮件。")
return
}
e := email.NewEmail()
e.From = "Next Terminal <" + username + ">"
e.To = []string{to}
e.Subject = subject
e.Text = []byte(text)
err := e.Send(host+":"+port, smtp.PlainAuth("", username, password, host))
if err != nil {
log.Errorf("邮件发送失败: %v", err.Error())
}
}

View File

@ -1,237 +0,0 @@
package service
import (
"os"
"next-terminal/pkg/guacd"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
type PropertyService struct {
propertyRepository *repository.PropertyRepository
}
func NewPropertyService(propertyRepository *repository.PropertyRepository) *PropertyService {
return &PropertyService{propertyRepository: propertyRepository}
}
func (r PropertyService) InitProperties() error {
propertyMap := r.propertyRepository.FindAllMap()
if len(propertyMap[guacd.Host]) == 0 {
property := model.Property{
Name: guacd.Host,
Value: "127.0.0.1",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.Port]) == 0 {
property := model.Property{
Name: guacd.Port,
Value: "4822",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableRecording]) == 0 {
property := model.Property{
Name: guacd.EnableRecording,
Value: "true",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.RecordingPath]) == 0 {
path, _ := os.Getwd()
property := model.Property{
Name: guacd.RecordingPath,
Value: path + "/recording/",
}
if !utils.FileExists(property.Value) {
if err := os.Mkdir(property.Value, os.ModePerm); err != nil {
return err
}
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.CreateRecordingPath]) == 0 {
property := model.Property{
Name: guacd.CreateRecordingPath,
Value: "true",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DriveName]) == 0 {
property := model.Property{
Name: guacd.DriveName,
Value: "File-System",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DrivePath]) == 0 {
path, _ := os.Getwd()
property := model.Property{
Name: guacd.DrivePath,
Value: path + "/drive/",
}
if !utils.FileExists(property.Value) {
if err := os.Mkdir(property.Value, os.ModePerm); err != nil {
return err
}
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.FontName]) == 0 {
property := model.Property{
Name: guacd.FontName,
Value: "menlo",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.FontSize]) == 0 {
property := model.Property{
Name: guacd.FontSize,
Value: "12",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.ColorScheme]) == 0 {
property := model.Property{
Name: guacd.ColorScheme,
Value: "gray-black",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableDrive]) == 0 {
property := model.Property{
Name: guacd.EnableDrive,
Value: "true",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableWallpaper]) == 0 {
property := model.Property{
Name: guacd.EnableWallpaper,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableTheming]) == 0 {
property := model.Property{
Name: guacd.EnableTheming,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableFontSmoothing]) == 0 {
property := model.Property{
Name: guacd.EnableFontSmoothing,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableFullWindowDrag]) == 0 {
property := model.Property{
Name: guacd.EnableFullWindowDrag,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableDesktopComposition]) == 0 {
property := model.Property{
Name: guacd.EnableDesktopComposition,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.EnableMenuAnimations]) == 0 {
property := model.Property{
Name: guacd.EnableMenuAnimations,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableBitmapCaching]) == 0 {
property := model.Property{
Name: guacd.DisableBitmapCaching,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableOffscreenCaching]) == 0 {
property := model.Property{
Name: guacd.DisableOffscreenCaching,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
if len(propertyMap[guacd.DisableGlyphCaching]) == 0 {
property := model.Property{
Name: guacd.DisableGlyphCaching,
Value: "false",
}
if err := r.propertyRepository.Create(&property); err != nil {
return err
}
}
return nil
}

View File

@ -1,32 +0,0 @@
package service
import (
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
type SessionService struct {
sessionRepository *repository.SessionRepository
}
func NewSessionService(sessionRepository *repository.SessionRepository) *SessionService {
return &SessionService{sessionRepository: sessionRepository}
}
func (r SessionService) Fix() {
sessions, _ := r.sessionRepository.FindByStatus(constant.Connected)
if sessions == nil {
return
}
for i := range sessions {
session := model.Session{
Status: constant.Disconnected,
DisconnectedTime: utils.NowJsonTime(),
}
_ = r.sessionRepository.UpdateById(&session, sessions[i].ID)
}
}

View File

@ -1,104 +0,0 @@
package service
import (
"next-terminal/pkg/log"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
)
type UserService struct {
userRepository *repository.UserRepository
loginLogRepository *repository.LoginLogRepository
}
func NewUserService(userRepository *repository.UserRepository, loginLogRepository *repository.LoginLogRepository) *UserService {
return &UserService{userRepository: userRepository, loginLogRepository: loginLogRepository}
}
func (r UserService) InitUser() (err error) {
users := r.userRepository.FindAll()
if len(users) == 0 {
initPassword := "admin"
var pass []byte
if pass, err = utils.Encoder.Encode([]byte(initPassword)); err != nil {
return err
}
user := model.User{
ID: utils.UUID(),
Username: "admin",
Password: string(pass),
Nickname: "超级管理员",
Type: constant.TypeAdmin,
Created: utils.NowJsonTime(),
}
if err := r.userRepository.Create(&user); err != nil {
return err
}
log.Infof("初始用户创建成功,账号:「%v」密码「%v」", user.Username, initPassword)
} else {
for i := range users {
// 修正默认用户类型为管理员
if users[i].Type == "" {
user := model.User{
Type: constant.TypeAdmin,
ID: users[i].ID,
}
if err := r.userRepository.Update(&user); err != nil {
return err
}
log.Infof("自动修正用户「%v」ID「%v」类型为管理员", users[i].Nickname, users[i].ID)
}
}
}
return nil
}
func (r UserService) FixedOnlineState() error {
// 修正用户登录状态
onlineUsers, err := r.userRepository.FindOnlineUsers()
if err != nil {
return err
}
for i := range onlineUsers {
logs, err := r.loginLogRepository.FindAliveLoginLogsByUserId(onlineUsers[i].ID)
if err != nil {
return err
}
if len(logs) == 0 {
if err := r.userRepository.UpdateOnline(onlineUsers[i].ID, false); err != nil {
return err
}
}
}
return nil
}
func (r UserService) Logout(token string) (err error) {
loginLog, err := r.loginLogRepository.FindById(token)
if err != nil {
log.Warnf("登录日志「%v」获取失败", token)
return
}
loginLogForUpdate := &model.LoginLog{LogoutTime: utils.NowJsonTime(), ID: token}
err = r.loginLogRepository.Update(loginLogForUpdate)
if err != nil {
return err
}
loginLogs, err := r.loginLogRepository.FindAliveLoginLogsByUserId(loginLog.UserId)
if err != nil {
return
}
if len(loginLogs) == 0 {
err = r.userRepository.UpdateOnline(loginLog.UserId, false)
}
return
}

View File

@ -1,107 +0,0 @@
package term
import (
"io"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type NextTerminal struct {
SshClient *ssh.Client
SshSession *ssh.Session
StdinPipe io.WriteCloser
SftpClient *sftp.Client
Recorder *Recorder
NextWriter *NextWriter
}
func NewNextTerminal(ip string, port int, username, password, privateKey, passphrase string, rows, cols int, recording string) (*NextTerminal, error) {
sshClient, err := NewSshClient(ip, port, username, password, privateKey, passphrase)
if err != nil {
return nil, err
}
sshSession, err := sshClient.NewSession()
if err != nil {
return nil, err
}
//defer sshSession.Close()
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
if err := sshSession.RequestPty("xterm-256color", rows, cols, modes); err != nil {
return nil, err
}
var nextWriter NextWriter
sshSession.Stdout = &nextWriter
sshSession.Stderr = &nextWriter
stdinPipe, err := sshSession.StdinPipe()
if err != nil {
return nil, err
}
if err := sshSession.Shell(); err != nil {
return nil, err
}
var recorder *Recorder
if recording != "" {
recorder, err = CreateRecording(recording, rows, cols)
if err != nil {
return nil, err
}
}
terminal := NextTerminal{
SshClient: sshClient,
SshSession: sshSession,
Recorder: recorder,
StdinPipe: stdinPipe,
NextWriter: &nextWriter,
}
return &terminal, nil
}
func (ret *NextTerminal) Write(p []byte) (int, error) {
return ret.StdinPipe.Write(p)
}
func (ret *NextTerminal) Read() ([]byte, int, error) {
bytes, n, err := ret.NextWriter.Read()
if err != nil {
return nil, 0, err
}
if ret.Recorder != nil && n > 0 {
_ = ret.Recorder.WriteData(string(bytes))
}
return bytes, n, nil
}
func (ret *NextTerminal) Close() error {
if ret.SshSession != nil {
return ret.SshSession.Close()
}
if ret.SshClient != nil {
return ret.SshClient.Close()
}
if ret.Recorder != nil {
return ret.Close()
}
return nil
}
func (ret *NextTerminal) WindowChange(h int, w int) error {
return ret.SshSession.WindowChange(h, w)
}

View File

@ -1,30 +0,0 @@
package term
import (
"bytes"
"sync"
)
type NextWriter struct {
b bytes.Buffer
mu sync.Mutex
}
func (w *NextWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.b.Write(p)
}
func (w *NextWriter) Read() ([]byte, int, error) {
w.mu.Lock()
defer w.mu.Unlock()
p := w.b.Bytes()
buf := make([]byte, len(p))
read, err := w.b.Read(buf)
w.b.Reset()
if err != nil {
return nil, 0, err
}
return buf, read, err
}

View File

@ -1,123 +0,0 @@
package term
import (
"encoding/json"
"os"
"time"
"next-terminal/server/utils"
)
type Env struct {
Shell string `json:"SHELL"`
Term string `json:"TERM"`
}
type Header struct {
Title string `json:"title"`
Version int `json:"version"`
Height int `json:"height"`
Width int `json:"width"`
Env Env `json:"env"`
Timestamp int `json:"Timestamp"`
}
type Recorder struct {
File *os.File
Timestamp int
}
func NewRecorder(recoding string) (recorder *Recorder, err error) {
recorder = &Recorder{}
parentDirectory := utils.GetParentDirectory(recoding)
if utils.FileExists(parentDirectory) {
if err := os.RemoveAll(parentDirectory); err != nil {
return nil, err
}
}
if err = os.MkdirAll(parentDirectory, 0777); err != nil {
return
}
var file *os.File
file, err = os.Create(recoding)
if err != nil {
return nil, err
}
recorder.File = file
return recorder, nil
}
func (recorder *Recorder) Close() {
if recorder.File != nil {
recorder.File.Close()
}
}
func (recorder *Recorder) WriteHeader(header *Header) (err error) {
var p []byte
if p, err = json.Marshal(header); err != nil {
return
}
if _, err := recorder.File.Write(p); err != nil {
return err
}
if _, err := recorder.File.Write([]byte("\n")); err != nil {
return err
}
recorder.Timestamp = header.Timestamp
return
}
func (recorder *Recorder) WriteData(data string) (err error) {
now := int(time.Now().UnixNano())
delta := float64(now-recorder.Timestamp*1000*1000*1000) / 1000 / 1000 / 1000
row := make([]interface{}, 0)
row = append(row, delta)
row = append(row, "o")
row = append(row, data)
var s []byte
if s, err = json.Marshal(row); err != nil {
return
}
if _, err := recorder.File.Write(s); err != nil {
return err
}
if _, err := recorder.File.Write([]byte("\n")); err != nil {
return err
}
return
}
func CreateRecording(recordingPath string, h int, w int) (*Recorder, error) {
recorder, err := NewRecorder(recordingPath)
if err != nil {
return nil, err
}
header := &Header{
Title: "",
Version: 2,
Height: 42,
Width: 150,
Env: Env{Shell: "/bin/bash", Term: "xterm-256color"},
Timestamp: int(time.Now().Unix()),
}
if err := recorder.WriteHeader(header); err != nil {
return nil, err
}
return recorder, nil
}

View File

@ -1,54 +0,0 @@
package term
import (
"fmt"
"time"
"golang.org/x/crypto/ssh"
)
func NewSshClient(ip string, port int, username, password, privateKey, passphrase string) (*ssh.Client, error) {
var authMethod ssh.AuthMethod
if username == "-" || username == "" {
username = "root"
}
if password == "-" {
password = ""
}
if privateKey == "-" {
privateKey = ""
}
if passphrase == "-" {
passphrase = ""
}
var err error
if privateKey != "" {
var key ssh.Signer
if len(passphrase) > 0 {
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase))
if err != nil {
return nil, err
}
} else {
key, err = ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
}
authMethod = ssh.PublicKeys(key)
} else {
authMethod = ssh.Password(password)
}
config := &ssh.ClientConfig{
Timeout: 1 * time.Second,
User: username,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
addr := fmt.Sprintf("%s:%d", ip, port)
return ssh.Dial("tcp", addr, config)
}

View File

@ -1,176 +0,0 @@
package main
import (
"fmt"
"io"
"os"
"time"
"next-terminal/pkg/log"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
type SSHTerminal struct {
Session *ssh.Session
exitMsg string
stdout io.Reader
stdin io.Writer
stderr io.Reader
}
func main() {
sshConfig := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("root"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "172.16.101.32:22", sshConfig)
if err != nil {
log.Error(err)
}
defer client.Close()
err = New(client)
if err != nil {
fmt.Println(err)
}
}
func (t *SSHTerminal) updateTerminalSize() {
go func() {
// SIGWINCH is sent to the process when the window size of the terminal has
// changed.
sigwinchCh := make(chan os.Signal, 1)
//signal.Notify(sigwinchCh, syscall.SIN)
fd := int(os.Stdin.Fd())
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
fmt.Println(err)
}
for {
select {
// The client updated the size of the local PTY. This change needs to occur
// on the server side PTY as well.
case sigwinch := <-sigwinchCh:
if sigwinch == nil {
return
}
currTermWidth, currTermHeight, err := terminal.GetSize(fd)
// Terminal size has not changed, don't do anything.
if currTermHeight == termHeight && currTermWidth == termWidth {
continue
}
err = t.Session.WindowChange(currTermHeight, currTermWidth)
if err != nil {
fmt.Printf("Unable to send window-change reqest: %s.", err)
continue
}
termWidth, termHeight = currTermWidth, currTermHeight
}
}
}()
}
func (t *SSHTerminal) interactiveSession() error {
defer func() {
if t.exitMsg == "" {
log.Info(os.Stdout, "the connection was closed on the remote side on ", time.Now().Format(time.RFC822))
} else {
log.Info(os.Stdout, t.exitMsg)
}
}()
fd := int(os.Stdin.Fd())
state, err := terminal.MakeRaw(fd)
if err != nil {
return err
}
defer terminal.Restore(fd, state)
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
return err
}
termType := os.Getenv("TERM")
if termType == "" {
termType = "xterm-256color"
}
err = t.Session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{})
if err != nil {
return err
}
t.updateTerminalSize()
t.stdin, err = t.Session.StdinPipe()
if err != nil {
return err
}
t.stdout, err = t.Session.StdoutPipe()
if err != nil {
return err
}
t.stderr, err = t.Session.StderrPipe()
go io.Copy(os.Stderr, t.stderr)
go io.Copy(os.Stdout, t.stdout)
go func() {
buf := make([]byte, 128)
for {
n, err := os.Stdin.Read(buf)
if err != nil {
fmt.Println(err)
return
}
if n > 0 {
_, err = t.stdin.Write(buf[:n])
if err != nil {
fmt.Println(err)
t.exitMsg = err.Error()
return
}
}
}
}()
err = t.Session.Shell()
if err != nil {
return err
}
err = t.Session.Wait()
if err != nil {
return err
}
return nil
}
func New(client *ssh.Client) error {
session, err := client.NewSession()
if err != nil {
return err
}
defer session.Close()
s := SSHTerminal{
Session: session,
}
return s.interactiveSession()
}

View File

@ -1,19 +0,0 @@
package totp
import (
otp_t "github.com/pquerna/otp"
totp_t "github.com/pquerna/otp/totp"
)
type GenerateOpts totp_t.GenerateOpts
func NewTOTP(opt GenerateOpts) (*otp_t.Key, error) {
return totp_t.Generate(totp_t.GenerateOpts(opt))
}
func Validate(code string, secret string) bool {
if secret == "" {
return true
}
return totp_t.Validate(code, secret)
}