waveterm/pkg/sstore/sstore.go

1295 lines
38 KiB
Go
Raw Normal View History

2022-06-13 20:11:56 +02:00
package sstore
import (
2022-07-01 21:17:19 +02:00
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"database/sql/driver"
"fmt"
2022-07-01 23:07:13 +02:00
"log"
2022-08-17 00:08:28 +02:00
"os"
"os/user"
2022-07-01 02:02:19 +02:00
"path"
"regexp"
"strings"
2022-06-21 06:57:23 +02:00
"sync"
2022-06-13 20:11:56 +02:00
"time"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/sawka/txwrap"
2022-07-01 21:17:19 +02:00
"github.com/scripthaus-dev/mshell/pkg/base"
"github.com/scripthaus-dev/mshell/pkg/packet"
"github.com/scripthaus-dev/sh2-server/pkg/dbutil"
2022-07-01 02:02:19 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
_ "github.com/mattn/go-sqlite3"
2022-06-13 20:11:56 +02:00
)
const LineTypeCmd = "cmd"
const LineTypeText = "text"
const LineNoHeight = -1
const DBFileName = "prompt.db"
const DBFileNameBackup = "backup.prompt.db"
2022-07-01 02:02:19 +02:00
2022-07-01 21:17:19 +02:00
const DefaultSessionName = "default"
const LocalRemoteAlias = "local"
2022-07-01 21:17:19 +02:00
2022-07-01 23:07:13 +02:00
const DefaultCwd = "~"
2023-03-02 09:31:19 +01:00
const (
MainViewSession = "session"
MainViewBookmarks = "bookmarks"
MainViewHistory = "history"
)
const (
CmdStatusRunning = "running"
CmdStatusDetached = "detached"
CmdStatusError = "error"
CmdStatusDone = "done"
CmdStatusHangup = "hangup"
CmdStatusWaiting = "waiting"
)
const (
RemoteAuthTypeNone = "none"
RemoteAuthTypePassword = "password"
RemoteAuthTypeKey = "key"
RemoteAuthTypeKeyPassword = "key+password"
)
const (
ShareModeLocal = "local"
ShareModeWeb = "web"
)
const (
ConnectModeStartup = "startup"
ConnectModeAuto = "auto"
ConnectModeManual = "manual"
)
const (
RemoteTypeSsh = "ssh"
)
2022-10-11 10:11:04 +02:00
const (
ScreenFocusInput = "input"
ScreenFocusCmd = "cmd"
ScreenFocusCmdFg = "cmd-fg"
2022-10-11 10:11:04 +02:00
)
2023-03-21 03:20:57 +01:00
const (
CmdStoreTypeSession = "session"
CmdStoreTypeScreen = "screen"
)
const (
UpdateType_ScreenNew = "screen:new"
UpdateType_ScreenDel = "screen:del"
UpdateType_ScreenSelectedLine = "screen:selectedline"
UpdateType_ScreenName = "screen:sharename"
UpdateType_LineNew = "line:new"
UpdateType_LineDel = "line:del"
UpdateType_LineRenderer = "line:renderer"
2023-03-31 09:32:38 +02:00
UpdateType_LineContentHeight = "line:contentheight"
UpdateType_CmdStatus = "cmd:status"
UpdateType_CmdTermOpts = "cmd:termopts"
UpdateType_CmdDoneInfo = "cmd:doneinfo"
UpdateType_CmdRtnState = "cmd:rtnstate"
UpdateType_PtyPos = "pty:pos"
)
2023-01-17 08:36:52 +01:00
const MaxTzNameLen = 50
2022-07-01 21:17:19 +02:00
var globalDBLock = &sync.Mutex{}
var globalDB *sqlx.DB
var globalDBErr error
func GetDBName() string {
scHome := scbase.GetPromptHomeDir()
2022-07-01 02:02:19 +02:00
return path.Join(scHome, DBFileName)
}
2022-06-13 20:11:56 +02:00
func GetDBBackupName() string {
scHome := scbase.GetPromptHomeDir()
return path.Join(scHome, DBFileNameBackup)
}
func IsValidConnectMode(mode string) bool {
2022-09-14 02:11:36 +02:00
return mode == ConnectModeStartup || mode == ConnectModeAuto || mode == ConnectModeManual
}
func GetDB(ctx context.Context) (*sqlx.DB, error) {
if txwrap.IsTxWrapContext(ctx) {
return nil, fmt.Errorf("cannot call GetDB from within a running transaction")
}
2022-07-01 21:17:19 +02:00
globalDBLock.Lock()
defer globalDBLock.Unlock()
if globalDB == nil && globalDBErr == nil {
dbName := GetDBName()
globalDB, globalDBErr = sqlx.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc&_journal_mode=WAL&_busy_timeout=5000", dbName))
if globalDBErr != nil {
globalDBErr = fmt.Errorf("opening db[%s]: %w", dbName, globalDBErr)
log.Printf("[db] error: %v\n", globalDBErr)
} else {
log.Printf("[db] successfully opened db %s\n", dbName)
}
}
2022-07-01 21:17:19 +02:00
return globalDB, globalDBErr
}
func CloseDB() {
globalDBLock.Lock()
defer globalDBLock.Unlock()
if globalDB == nil {
return
}
err := globalDB.Close()
if err != nil {
log.Printf("[db] error closing database: %v\n", err)
}
globalDB = nil
}
2023-03-26 22:21:58 +02:00
type CmdPtr struct {
ScreenId string
CmdId string
}
2022-09-25 09:26:33 +02:00
type ClientWinSizeType struct {
Width int `json:"width"`
Height int `json:"height"`
Top int `json:"top"`
Left int `json:"left"`
FullScreen bool `json:"fullscreen,omitempty"`
}
2023-01-17 08:36:52 +01:00
type ActivityUpdate struct {
FgMinutes int
ActiveMinutes int
OpenMinutes int
NumCommands int
2023-02-22 07:41:56 +01:00
ClickShared int
HistoryView int
BookmarksView int
NumConns int
2023-02-24 00:17:47 +01:00
BuildTime string
2023-01-17 08:36:52 +01:00
}
type ActivityType struct {
Day string `json:"day"`
Uploaded bool `json:"-"`
TData TelemetryData `json:"tdata"`
TzName string `json:"tzname"`
TzOffset int `json:"tzoffset"`
ClientVersion string `json:"clientversion"`
ClientArch string `json:"clientarch"`
2023-02-24 00:17:47 +01:00
BuildTime string `json:"buildtime"`
OSRelease string `json:"osrelease"`
}
type TelemetryData struct {
NumCommands int `json:"numcommands"`
ActiveMinutes int `json:"activeminutes"`
FgMinutes int `json:"fgminutes"`
OpenMinutes int `json:"openminutes"`
2023-02-22 07:41:56 +01:00
ClickShared int `json:"clickshared,omitempty"`
HistoryView int `json:"historyview,omitempty"`
BookmarksView int `json:"bookmarksview,omitempty"`
NumConns int `json:"numconns"`
}
func (tdata TelemetryData) Value() (driver.Value, error) {
return quickValueJson(tdata)
}
func (tdata *TelemetryData) Scan(val interface{}) error {
return quickScanJson(tdata, val)
2023-01-17 08:36:52 +01:00
}
type ClientOptsType struct {
2023-04-05 06:52:20 +02:00
NoTelemetry bool `json:"notelemetry,omitempty"`
AcceptedTos int64 `json:"acceptedtos,omitempty"`
}
type FeOptsType struct {
TermFontSize int `json:"termfontsize,omitempty"`
}
2023-03-21 03:20:57 +01:00
type ClientMigrationData struct {
MigrationType string `json:"migrationtype"`
MigrationPos int `json:"migrationpos"`
MigrationTotal int `json:"migrationtotal"`
MigrationDone bool `json:"migrationdone"`
}
type ClientData struct {
2023-03-21 03:20:57 +01:00
ClientId string `json:"clientid"`
UserId string `json:"userid"`
UserPrivateKeyBytes []byte `json:"-"`
UserPublicKeyBytes []byte `json:"-"`
UserPrivateKey *ecdsa.PrivateKey `json:"-" dbmap:"-"`
UserPublicKey *ecdsa.PublicKey `json:"-" dbmap:"-"`
ActiveSessionId string `json:"activesessionid"`
WinSize ClientWinSizeType `json:"winsize"`
ClientOpts ClientOptsType `json:"clientopts"`
FeOpts FeOptsType `json:"feopts"`
CmdStoreType string `json:"cmdstoretype"`
Migration *ClientMigrationData `json:"migration,omitempty" dbmap:"-"`
DBVersion int `json:"dbversion" dbmap:"-"`
}
func (ClientData) UseDBMap() {}
2022-09-22 07:02:38 +02:00
2022-06-21 06:57:23 +02:00
type SessionType struct {
SessionId string `json:"sessionid"`
Name string `json:"name"`
SessionIdx int64 `json:"sessionidx"`
ActiveScreenId string `json:"activescreenid"`
ShareMode string `json:"sharemode"`
NotifyNum int64 `json:"notifynum"`
2022-12-25 22:03:11 +01:00
Archived bool `json:"archived,omitempty"`
2022-12-25 22:21:48 +01:00
ArchivedTs int64 `json:"archivedts,omitempty"`
Remotes []*RemoteInstance `json:"remotes"`
2022-07-15 03:39:40 +02:00
// only for updates
Remove bool `json:"remove,omitempty"`
Full bool `json:"full,omitempty"`
2022-07-12 23:27:16 +02:00
}
2022-09-20 23:15:20 +02:00
type SessionStatsType struct {
2022-12-25 22:03:11 +01:00
SessionId string `json:"sessionid"`
NumScreens int `json:"numscreens"`
NumArchivedScreens int `json:"numarchivedscreens"`
NumLines int `json:"numlines"`
NumCmds int `json:"numcmds"`
DiskStats SessionDiskSizeType `json:"diskstats"`
2022-09-20 23:15:20 +02:00
}
var RemoteNameRe = regexp.MustCompile("^\\*?[a-zA-Z0-9_-]+$")
type RemotePtrType struct {
2022-08-24 22:21:54 +02:00
OwnerId string `json:"ownerid"`
RemoteId string `json:"remoteid"`
Name string `json:"name"`
}
func (r RemotePtrType) IsSessionScope() bool {
return strings.HasPrefix(r.Name, "*")
}
2022-12-31 02:01:17 +01:00
func (rptr *RemotePtrType) GetDisplayName(baseDisplayName string) string {
name := baseDisplayName
if rptr == nil {
return name
}
if rptr.Name != "" {
name = name + ":" + rptr.Name
}
if rptr.OwnerId != "" {
name = "@" + rptr.OwnerId + ":" + name
}
return name
}
func (r RemotePtrType) Validate() error {
if r.OwnerId != "" {
if _, err := uuid.Parse(r.OwnerId); err != nil {
return fmt.Errorf("invalid ownerid format: %v", err)
}
}
if r.RemoteId != "" {
if _, err := uuid.Parse(r.RemoteId); err != nil {
return fmt.Errorf("invalid remoteid format: %v", err)
}
}
if r.Name != "" {
ok := RemoteNameRe.MatchString(r.Name)
if !ok {
return fmt.Errorf("invalid remote name")
}
}
return nil
}
func (r RemotePtrType) MakeFullRemoteRef() string {
if r.RemoteId == "" {
return ""
}
2022-08-24 22:21:54 +02:00
if r.OwnerId == "" && r.Name == "" {
return r.RemoteId
}
2022-08-24 22:21:54 +02:00
if r.OwnerId != "" && r.Name == "" {
return fmt.Sprintf("@%s:%s", r.OwnerId, r.RemoteId)
}
2022-08-24 22:21:54 +02:00
if r.OwnerId == "" && r.Name != "" {
return fmt.Sprintf("%s:%s", r.RemoteId, r.Name)
}
2022-08-24 22:21:54 +02:00
return fmt.Sprintf("@%s:%s:%s", r.OwnerId, r.RemoteId, r.Name)
}
func (h *HistoryItemType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["historyid"] = h.HistoryId
rtn["ts"] = h.Ts
rtn["userid"] = h.UserId
rtn["sessionid"] = h.SessionId
rtn["screenid"] = h.ScreenId
rtn["lineid"] = h.LineId
2023-03-18 19:49:00 +01:00
rtn["linenum"] = h.LineNum
rtn["haderror"] = h.HadError
rtn["cmdid"] = h.CmdId
rtn["cmdstr"] = h.CmdStr
rtn["remoteownerid"] = h.Remote.OwnerId
rtn["remoteid"] = h.Remote.RemoteId
rtn["remotename"] = h.Remote.Name
rtn["ismetacmd"] = h.IsMetaCmd
rtn["incognito"] = h.Incognito
return rtn
}
func (h *HistoryItemType) FromMap(m map[string]interface{}) bool {
quickSetStr(&h.HistoryId, m, "historyid")
quickSetInt64(&h.Ts, m, "ts")
quickSetStr(&h.UserId, m, "userid")
quickSetStr(&h.SessionId, m, "sessionid")
quickSetStr(&h.ScreenId, m, "screenid")
quickSetStr(&h.LineId, m, "lineid")
quickSetBool(&h.HadError, m, "haderror")
quickSetStr(&h.CmdId, m, "cmdid")
quickSetStr(&h.CmdStr, m, "cmdstr")
quickSetStr(&h.Remote.OwnerId, m, "remoteownerid")
quickSetStr(&h.Remote.RemoteId, m, "remoteid")
quickSetStr(&h.Remote.Name, m, "remotename")
quickSetBool(&h.IsMetaCmd, m, "ismetacmd")
2022-08-30 04:18:02 +02:00
quickSetStr(&h.HistoryNum, m, "historynum")
quickSetInt64(&h.LineNum, m, "linenum")
quickSetBool(&h.Incognito, m, "incognito")
return true
}
type ScreenOptsType struct {
2022-08-27 06:44:18 +02:00
TabColor string `json:"tabcolor,omitempty"`
PTerm string `json:"pterm,omitempty"`
}
type ScreenLinesType struct {
ScreenId string `json:"screenid"`
Lines []*LineType `json:"lines" dbmap:"-"`
Cmds []*CmdType `json:"cmds" dbmap:"-"`
}
func (ScreenLinesType) UseDBMap() {}
type ScreenWebShareOpts struct {
2023-04-05 07:28:52 +02:00
ShareName string `json:"sharename"`
ViewKey string `json:"viewkey"`
}
2022-07-12 23:27:16 +02:00
type ScreenType struct {
SessionId string `json:"sessionid"`
ScreenId string `json:"screenid"`
Name string `json:"name"`
ScreenIdx int64 `json:"screenidx"`
ScreenOpts ScreenOptsType `json:"screenopts"`
OwnerId string `json:"ownerid"`
ShareMode string `json:"sharemode"`
WebShareOpts *ScreenWebShareOpts `json:"webshareopts,omitempty"`
CurRemote RemotePtrType `json:"curremote"`
NextLineNum int64 `json:"nextlinenum"`
SelectedLine int64 `json:"selectedline"`
Anchor ScreenAnchorType `json:"anchor"`
FocusType string `json:"focustype"`
Archived bool `json:"archived,omitempty"`
ArchivedTs int64 `json:"archivedts,omitempty"`
2022-07-15 03:39:40 +02:00
// only for updates
2023-03-13 20:10:23 +01:00
Full bool `json:"full,omitempty"`
2022-07-15 03:39:40 +02:00
Remove bool `json:"remove,omitempty"`
2022-07-12 23:27:16 +02:00
}
func (s *ScreenType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["sessionid"] = s.SessionId
rtn["screenid"] = s.ScreenId
rtn["name"] = s.Name
rtn["screenidx"] = s.ScreenIdx
rtn["screenopts"] = quickJson(s.ScreenOpts)
rtn["ownerid"] = s.OwnerId
rtn["sharemode"] = s.ShareMode
rtn["webshareopts"] = quickNullableJson(s.WebShareOpts)
rtn["curremoteownerid"] = s.CurRemote.OwnerId
rtn["curremoteid"] = s.CurRemote.RemoteId
rtn["curremotename"] = s.CurRemote.Name
rtn["nextlinenum"] = s.NextLineNum
rtn["selectedline"] = s.SelectedLine
rtn["anchor"] = quickJson(s.Anchor)
rtn["focustype"] = s.FocusType
rtn["archived"] = s.Archived
rtn["archivedts"] = s.ArchivedTs
return rtn
}
func (s *ScreenType) FromMap(m map[string]interface{}) bool {
quickSetStr(&s.SessionId, m, "sessionid")
quickSetStr(&s.ScreenId, m, "screenid")
quickSetStr(&s.Name, m, "name")
quickSetInt64(&s.ScreenIdx, m, "screenidx")
quickSetJson(&s.ScreenOpts, m, "screenopts")
quickSetStr(&s.OwnerId, m, "ownerid")
quickSetStr(&s.ShareMode, m, "sharemode")
quickSetNullableJson(&s.WebShareOpts, m, "webshareopts")
quickSetStr(&s.CurRemote.OwnerId, m, "curremoteownerid")
quickSetStr(&s.CurRemote.RemoteId, m, "curremoteid")
quickSetStr(&s.CurRemote.Name, m, "curremotename")
quickSetInt64(&s.NextLineNum, m, "nextlinenum")
quickSetInt64(&s.SelectedLine, m, "selectedline")
quickSetJson(&s.Anchor, m, "anchor")
quickSetStr(&s.FocusType, m, "focustype")
quickSetBool(&s.Archived, m, "archived")
quickSetInt64(&s.ArchivedTs, m, "archivedts")
return true
}
const (
LayoutFull = "full"
)
2022-07-12 23:27:16 +02:00
type LayoutType struct {
Type string `json:"type"`
Parent string `json:"parent,omitempty"`
ZIndex int64 `json:"zindex,omitempty"`
Float bool `json:"float,omitempty"`
Top string `json:"top,omitempty"`
Bottom string `json:"bottom,omitempty"`
Left string `json:"left,omitempty"`
Right string `json:"right,omitempty"`
Width string `json:"width,omitempty"`
Height string `json:"height,omitempty"`
2022-07-12 23:27:16 +02:00
}
func (l *LayoutType) Scan(val interface{}) error {
return quickScanJson(l, val)
}
func (l LayoutType) Value() (driver.Value, error) {
2022-07-12 23:27:16 +02:00
return quickValueJson(l)
}
type ScreenAnchorType struct {
2022-10-11 10:11:04 +02:00
AnchorLine int `json:"anchorline,omitempty"`
AnchorOffset int `json:"anchoroffset,omitempty"`
}
2022-07-12 22:50:44 +02:00
type HistoryItemType struct {
HistoryId string `json:"historyid"`
Ts int64 `json:"ts"`
UserId string `json:"userid"`
SessionId string `json:"sessionid"`
ScreenId string `json:"screenid"`
LineId string `json:"lineid"`
HadError bool `json:"haderror"`
CmdId string `json:"cmdid"`
CmdStr string `json:"cmdstr"`
Remote RemotePtrType `json:"remote"`
IsMetaCmd bool `json:"ismetacmd"`
Incognito bool `json:"incognito,omitempty"`
2022-08-11 21:07:41 +02:00
// only for updates
Remove bool `json:"remove"`
// transient (string because of different history orderings)
HistoryNum string `json:"historynum"`
LineNum int64 `json:"linenum"`
}
2022-08-30 04:18:02 +02:00
type HistoryQueryOpts struct {
2023-03-02 09:31:19 +01:00
Offset int
MaxItems int
FromTs int64
SearchText string
SessionId string
RemoteId string
2023-03-15 00:37:22 +01:00
ScreenId string
NoMeta bool
RawOffset int
2023-03-06 22:54:38 +01:00
FilterFn func(*HistoryItemType) bool
}
type HistoryQueryResult struct {
MaxItems int
Items []*HistoryItemType
Offset int // the offset shown to user
2023-03-06 22:54:38 +01:00
RawOffset int // internal offset
HasMore bool
NextRawOffset int // internal offset used by pager for next query
2023-03-06 22:54:38 +01:00
prevItems int // holds number of items skipped by RawOffset
2022-08-30 04:18:02 +02:00
}
2022-07-07 09:10:37 +02:00
type TermOpts struct {
Rows int64 `json:"rows"`
Cols int64 `json:"cols"`
FlexRows bool `json:"flexrows,omitempty"`
MaxPtySize int64 `json:"maxptysize,omitempty"`
2022-07-07 09:10:37 +02:00
}
func (opts *TermOpts) Scan(val interface{}) error {
2022-07-12 23:27:16 +02:00
return quickScanJson(opts, val)
2022-07-07 09:10:37 +02:00
}
func (opts TermOpts) Value() (driver.Value, error) {
2022-07-12 23:27:16 +02:00
return quickValueJson(opts)
2022-07-07 09:10:37 +02:00
}
type ShellStatePtr struct {
BaseHash string
DiffHashArr []string
}
func (ssptr *ShellStatePtr) IsEmpty() bool {
if ssptr == nil || ssptr.BaseHash == "" {
return true
}
return false
}
type RemoteInstance struct {
2022-11-28 09:13:00 +01:00
RIId string `json:"riid"`
Name string `json:"name"`
SessionId string `json:"sessionid"`
2023-03-15 00:37:22 +01:00
ScreenId string `json:"screenid"`
2022-11-28 09:13:00 +01:00
RemoteOwnerId string `json:"remoteownerid"`
RemoteId string `json:"remoteid"`
FeState FeStateType `json:"festate"`
StateBaseHash string `json:"-"`
StateDiffHashArr []string `json:"-"`
2022-08-11 03:33:32 +02:00
// only for updates
Remove bool `json:"remove,omitempty"`
2022-06-21 06:57:23 +02:00
}
2022-11-28 09:13:00 +01:00
type StateBase struct {
BaseHash string
Version string
Ts int64
Data []byte
}
type StateDiff struct {
DiffHash string
Ts int64
BaseHash string
DiffHashArr []string
Data []byte
}
func (sd *StateDiff) FromMap(m map[string]interface{}) bool {
2022-11-28 09:13:00 +01:00
quickSetStr(&sd.DiffHash, m, "diffhash")
quickSetInt64(&sd.Ts, m, "ts")
quickSetStr(&sd.BaseHash, m, "basehash")
quickSetJsonArr(&sd.DiffHashArr, m, "diffhasharr")
quickSetBytes(&sd.Data, m, "data")
return true
2022-11-28 09:13:00 +01:00
}
func (sd *StateDiff) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
2022-11-28 09:13:00 +01:00
rtn["diffhash"] = sd.DiffHash
rtn["ts"] = sd.Ts
rtn["basehash"] = sd.BaseHash
rtn["diffhasharr"] = quickJsonArr(sd.DiffHashArr)
rtn["data"] = sd.Data
return rtn
}
2022-11-28 09:13:00 +01:00
type FeStateType struct {
Cwd string `json:"cwd"`
// maybe later we can add some vars
}
func FeStateFromShellState(state *packet.ShellState) *FeStateType {
if state == nil {
return nil
}
return &FeStateType{Cwd: state.Cwd}
}
func (ri *RemoteInstance) FromMap(m map[string]interface{}) bool {
quickSetStr(&ri.RIId, m, "riid")
quickSetStr(&ri.Name, m, "name")
quickSetStr(&ri.SessionId, m, "sessionid")
2023-03-15 00:37:22 +01:00
quickSetStr(&ri.ScreenId, m, "screenid")
quickSetStr(&ri.RemoteOwnerId, m, "remoteownerid")
quickSetStr(&ri.RemoteId, m, "remoteid")
2022-11-28 09:13:00 +01:00
quickSetJson(&ri.FeState, m, "festate")
quickSetStr(&ri.StateBaseHash, m, "statebasehash")
quickSetJsonArr(&ri.StateDiffHashArr, m, "statediffhasharr")
return true
}
2022-11-28 09:13:00 +01:00
func (ri *RemoteInstance) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["riid"] = ri.RIId
rtn["name"] = ri.Name
rtn["sessionid"] = ri.SessionId
2023-03-15 00:37:22 +01:00
rtn["screenid"] = ri.ScreenId
2022-11-28 09:13:00 +01:00
rtn["remoteownerid"] = ri.RemoteOwnerId
rtn["remoteid"] = ri.RemoteId
rtn["festate"] = quickJson(ri.FeState)
rtn["statebasehash"] = ri.StateBaseHash
rtn["statediffhasharr"] = quickJsonArr(ri.StateDiffHashArr)
return rtn
}
type ScreenUpdateType struct {
UpdateId int64 `json:"updateid"`
ScreenId string `json:"screenid"`
LineId string `json:"lineid"`
UpdateType string `json:"updatetype"`
UpdateTs int64 `json:"updatets"`
}
func (ScreenUpdateType) UseDBMap() {}
2022-06-13 20:11:56 +02:00
type LineType struct {
2023-03-15 00:37:22 +01:00
ScreenId string `json:"screenid"`
UserId string `json:"userid"`
LineId string `json:"lineid"`
Ts int64 `json:"ts"`
LineNum int64 `json:"linenum"`
LineNumTemp bool `json:"linenumtemp,omitempty"`
LineLocal bool `json:"linelocal"`
LineType string `json:"linetype"`
2023-02-06 09:30:23 +01:00
Renderer string `json:"renderer,omitempty"`
Text string `json:"text,omitempty"`
CmdId string `json:"cmdid,omitempty"`
Ephemeral bool `json:"ephemeral,omitempty"`
ContentHeight int64 `json:"contentheight,omitempty"`
Star bool `json:"star,omitempty"`
Archived bool `json:"archived,omitempty"`
Remove bool `json:"remove,omitempty"`
}
2023-03-02 09:31:19 +01:00
type PlaybookType struct {
PlaybookId string `json:"playbookid"`
PlaybookName string `json:"playbookname"`
Description string `json:"description"`
EntryIds []string `json:"entryids"`
// this is not persisted to DB, just for transport to FE
Entries []*PlaybookEntry `json:"entries"`
}
func (p *PlaybookType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["playbookid"] = p.PlaybookId
rtn["playbookname"] = p.PlaybookName
rtn["description"] = p.Description
rtn["entryids"] = quickJsonArr(p.EntryIds)
return rtn
}
func (p *PlaybookType) FromMap(m map[string]interface{}) bool {
2023-03-02 09:31:19 +01:00
quickSetStr(&p.PlaybookId, m, "playbookid")
quickSetStr(&p.PlaybookName, m, "playbookname")
quickSetStr(&p.Description, m, "description")
quickSetJsonArr(&p.Entries, m, "entries")
return true
2023-03-02 09:31:19 +01:00
}
// reorders p.Entries to match p.EntryIds
func (p *PlaybookType) OrderEntries() {
if len(p.Entries) == 0 {
return
}
m := make(map[string]*PlaybookEntry)
for _, entry := range p.Entries {
m[entry.EntryId] = entry
}
newList := make([]*PlaybookEntry, 0, len(p.EntryIds))
for _, entryId := range p.EntryIds {
entry := m[entryId]
if entry != nil {
newList = append(newList, entry)
}
}
p.Entries = newList
}
// removes from p.EntryIds (not from p.Entries)
func (p *PlaybookType) RemoveEntry(entryIdToRemove string) {
if len(p.EntryIds) == 0 {
return
}
newList := make([]string, 0, len(p.EntryIds)-1)
for _, entryId := range p.EntryIds {
if entryId == entryIdToRemove {
continue
}
newList = append(newList, entryId)
}
p.EntryIds = newList
}
type PlaybookEntry struct {
PlaybookId string `json:"playbookid"`
EntryId string `json:"entryid"`
Alias string `json:"alias"`
CmdStr string `json:"cmdstr"`
UpdatedTs int64 `json:"updatedts"`
CreatedTs int64 `json:"createdts"`
Description string `json:"description"`
Remove bool `json:"remove,omitempty"`
}
2023-02-21 06:39:29 +01:00
type BookmarkType struct {
BookmarkId string `json:"bookmarkid"`
CreatedTs int64 `json:"createdts"`
CmdStr string `json:"cmdstr"`
Alias string `json:"alias,omitempty"`
Tags []string `json:"tags"`
Description string `json:"description"`
OrderIdx int64 `json:"orderidx"`
Remove bool `json:"remove,omitempty"`
2023-02-21 06:39:29 +01:00
}
func (bm *BookmarkType) GetSimpleKey() string {
return bm.BookmarkId
}
2023-02-21 06:39:29 +01:00
func (bm *BookmarkType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["bookmarkid"] = bm.BookmarkId
rtn["createdts"] = bm.CreatedTs
rtn["cmdstr"] = bm.CmdStr
rtn["alias"] = bm.Alias
rtn["description"] = bm.Description
rtn["tags"] = quickJsonArr(bm.Tags)
return rtn
}
func (bm *BookmarkType) FromMap(m map[string]interface{}) bool {
2023-02-21 06:39:29 +01:00
quickSetStr(&bm.BookmarkId, m, "bookmarkid")
quickSetInt64(&bm.CreatedTs, m, "createdts")
quickSetStr(&bm.Alias, m, "alias")
quickSetStr(&bm.CmdStr, m, "cmdstr")
quickSetStr(&bm.Description, m, "description")
quickSetJsonArr(&bm.Tags, m, "tags")
return true
2023-02-21 06:39:29 +01:00
}
type ResolveItem struct {
2022-12-24 00:56:29 +01:00
Name string
Num int
Id string
Hidden bool
}
type SSHOpts struct {
2022-10-01 01:23:40 +02:00
Local bool `json:"local,omitempty"`
SSHHost string `json:"sshhost"`
SSHUser string `json:"sshuser"`
2022-10-01 01:23:40 +02:00
SSHOptsStr string `json:"sshopts,omitempty"`
SSHIdentity string `json:"sshidentity,omitempty"`
SSHPort int `json:"sshport,omitempty"`
2022-10-01 02:22:28 +02:00
SSHPassword string `json:"sshpassword,omitempty"`
}
func (opts SSHOpts) GetAuthType() string {
if opts.SSHPassword != "" && opts.SSHIdentity != "" {
return RemoteAuthTypeKeyPassword
}
if opts.SSHIdentity != "" {
return RemoteAuthTypeKey
}
if opts.SSHPassword != "" {
return RemoteAuthTypePassword
}
return RemoteAuthTypeNone
}
type RemoteOptsType struct {
Color string `json:"color"`
}
func (opts *RemoteOptsType) Scan(val interface{}) error {
return quickScanJson(opts, val)
}
func (opts RemoteOptsType) Value() (driver.Value, error) {
return quickValueJson(opts)
}
type RemoteType struct {
RemoteId string `json:"remoteid"`
PhysicalId string `json:"physicalid"`
RemoteType string `json:"remotetype"`
RemoteAlias string `json:"remotealias"`
RemoteCanonicalName string `json:"remotecanonicalname"`
RemoteSudo bool `json:"remotesudo"`
RemoteUser string `json:"remoteuser"`
RemoteHost string `json:"remotehost"`
ConnectMode string `json:"connectmode"`
AutoInstall bool `json:"autoinstall"`
SSHOpts *SSHOpts `json:"sshopts"`
RemoteOpts *RemoteOptsType `json:"remoteopts"`
LastConnectTs int64 `json:"lastconnectts"`
Archived bool `json:"archived"`
RemoteIdx int64 `json:"remoteidx"`
Local bool `json:"local"`
StateVars map[string]string `json:"statevars"`
2022-08-17 00:08:28 +02:00
}
func (r *RemoteType) GetName() string {
if r.RemoteAlias != "" {
return r.RemoteAlias
}
2022-09-14 02:11:36 +02:00
return r.RemoteCanonicalName
}
type CmdDoneInfo struct {
Ts int64 `json:"ts"`
ExitCode int64 `json:"exitcode"`
DurationMs int64 `json:"durationms"`
}
2023-03-21 03:20:57 +01:00
type CmdMapType struct {
SessionId string `json:"sessionid"`
ScreenId string `json:"screenid"`
CmdId string `json:"cmdid"`
}
type CmdType struct {
ScreenId string `json:"screenid"`
2022-09-22 07:02:38 +02:00
CmdId string `json:"cmdid"`
Remote RemotePtrType `json:"remote"`
CmdStr string `json:"cmdstr"`
2023-03-21 03:20:57 +01:00
RawCmdStr string `json:"rawcmdstr"`
FeState FeStateType `json:"festate"`
StatePtr ShellStatePtr `json:"state"`
2022-09-22 07:02:38 +02:00
TermOpts TermOpts `json:"termopts"`
OrigTermOpts TermOpts `json:"origtermopts"`
Status string `json:"status"`
StartPk *packet.CmdStartPacketType `json:"startpk,omitempty"`
DoneInfo *CmdDoneInfo `json:"doneinfo,omitempty"`
RunOut []packet.PacketType `json:"runout,omitempty"`
RtnState bool `json:"rtnstate,omitempty"`
RtnStatePtr ShellStatePtr `json:"rtnstateptr,omitempty"`
Remove bool `json:"remove,omitempty"`
2022-07-07 09:10:37 +02:00
}
func (r *RemoteType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["remoteid"] = r.RemoteId
2022-08-17 00:08:28 +02:00
rtn["physicalid"] = r.PhysicalId
rtn["remotetype"] = r.RemoteType
2022-08-17 00:08:28 +02:00
rtn["remotealias"] = r.RemoteAlias
rtn["remotecanonicalname"] = r.RemoteCanonicalName
rtn["remotesudo"] = r.RemoteSudo
rtn["remoteuser"] = r.RemoteUser
rtn["remotehost"] = r.RemoteHost
rtn["connectmode"] = r.ConnectMode
rtn["autoinstall"] = r.AutoInstall
rtn["sshopts"] = quickJson(r.SSHOpts)
rtn["remoteopts"] = quickJson(r.RemoteOpts)
rtn["lastconnectts"] = r.LastConnectTs
2022-09-14 02:11:36 +02:00
rtn["archived"] = r.Archived
2022-09-14 21:06:55 +02:00
rtn["remoteidx"] = r.RemoteIdx
rtn["local"] = r.Local
rtn["statevars"] = quickJson(r.StateVars)
return rtn
}
func (r *RemoteType) FromMap(m map[string]interface{}) bool {
quickSetStr(&r.RemoteId, m, "remoteid")
2022-08-17 00:08:28 +02:00
quickSetStr(&r.PhysicalId, m, "physicalid")
quickSetStr(&r.RemoteType, m, "remotetype")
2022-08-17 00:08:28 +02:00
quickSetStr(&r.RemoteAlias, m, "remotealias")
quickSetStr(&r.RemoteCanonicalName, m, "remotecanonicalname")
quickSetBool(&r.RemoteSudo, m, "remotesudo")
quickSetStr(&r.RemoteUser, m, "remoteuser")
quickSetStr(&r.RemoteHost, m, "remotehost")
quickSetStr(&r.ConnectMode, m, "connectmode")
quickSetBool(&r.AutoInstall, m, "autoinstall")
quickSetJson(&r.SSHOpts, m, "sshopts")
quickSetJson(&r.RemoteOpts, m, "remoteopts")
quickSetInt64(&r.LastConnectTs, m, "lastconnectts")
2022-09-14 02:11:36 +02:00
quickSetBool(&r.Archived, m, "archived")
2022-09-14 21:06:55 +02:00
quickSetInt64(&r.RemoteIdx, m, "remoteidx")
quickSetBool(&r.Local, m, "local")
quickSetJson(&r.StateVars, m, "statevars")
return true
2022-07-07 09:10:37 +02:00
}
func (cmd *CmdType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["screenid"] = cmd.ScreenId
2022-07-07 09:10:37 +02:00
rtn["cmdid"] = cmd.CmdId
2022-08-24 22:21:54 +02:00
rtn["remoteownerid"] = cmd.Remote.OwnerId
rtn["remoteid"] = cmd.Remote.RemoteId
rtn["remotename"] = cmd.Remote.Name
2022-07-07 09:10:37 +02:00
rtn["cmdstr"] = cmd.CmdStr
2023-03-21 03:20:57 +01:00
rtn["rawcmdstr"] = cmd.RawCmdStr
rtn["festate"] = quickJson(cmd.FeState)
rtn["statebasehash"] = cmd.StatePtr.BaseHash
rtn["statediffhasharr"] = quickJsonArr(cmd.StatePtr.DiffHashArr)
2022-07-07 09:10:37 +02:00
rtn["termopts"] = quickJson(cmd.TermOpts)
2022-09-22 07:02:38 +02:00
rtn["origtermopts"] = quickJson(cmd.OrigTermOpts)
2022-07-07 09:10:37 +02:00
rtn["status"] = cmd.Status
rtn["startpk"] = quickJson(cmd.StartPk)
rtn["doneinfo"] = quickJson(cmd.DoneInfo)
2022-07-07 09:10:37 +02:00
rtn["runout"] = quickJson(cmd.RunOut)
rtn["rtnstate"] = cmd.RtnState
rtn["rtnbasehash"] = cmd.RtnStatePtr.BaseHash
rtn["rtndiffhasharr"] = quickJsonArr(cmd.RtnStatePtr.DiffHashArr)
2022-07-07 09:10:37 +02:00
return rtn
}
func (cmd *CmdType) FromMap(m map[string]interface{}) bool {
quickSetStr(&cmd.ScreenId, m, "screenid")
2022-07-07 09:10:37 +02:00
quickSetStr(&cmd.CmdId, m, "cmdid")
2022-08-24 22:21:54 +02:00
quickSetStr(&cmd.Remote.OwnerId, m, "remoteownerid")
quickSetStr(&cmd.Remote.RemoteId, m, "remoteid")
quickSetStr(&cmd.Remote.Name, m, "remotename")
2022-07-07 09:10:37 +02:00
quickSetStr(&cmd.CmdStr, m, "cmdstr")
2023-03-21 03:20:57 +01:00
quickSetStr(&cmd.RawCmdStr, m, "rawcmdstr")
quickSetJson(&cmd.FeState, m, "festate")
quickSetStr(&cmd.StatePtr.BaseHash, m, "statebasehash")
quickSetJsonArr(&cmd.StatePtr.DiffHashArr, m, "statediffhasharr")
2022-07-07 09:10:37 +02:00
quickSetJson(&cmd.TermOpts, m, "termopts")
2022-09-22 07:02:38 +02:00
quickSetJson(&cmd.OrigTermOpts, m, "origtermopts")
2022-07-07 09:10:37 +02:00
quickSetStr(&cmd.Status, m, "status")
quickSetJson(&cmd.StartPk, m, "startpk")
quickSetJson(&cmd.DoneInfo, m, "doneinfo")
2022-07-07 09:10:37 +02:00
quickSetJson(&cmd.RunOut, m, "runout")
quickSetBool(&cmd.RtnState, m, "rtnstate")
quickSetStr(&cmd.RtnStatePtr.BaseHash, m, "rtnbasehash")
quickSetJsonArr(&cmd.RtnStatePtr.DiffHashArr, m, "rtndiffhasharr")
return true
2022-06-13 20:11:56 +02:00
}
2023-03-21 03:20:57 +01:00
func makeNewLineCmd(screenId string, userId string, cmdId string, renderer string) *LineType {
2022-06-13 20:11:56 +02:00
rtn := &LineType{}
2023-03-15 00:37:22 +01:00
rtn.ScreenId = screenId
rtn.UserId = userId
rtn.LineId = scbase.GenPromptUUID()
2022-06-13 20:11:56 +02:00
rtn.Ts = time.Now().UnixMilli()
rtn.LineLocal = true
2022-06-13 20:11:56 +02:00
rtn.LineType = LineTypeCmd
rtn.CmdId = cmdId
rtn.ContentHeight = LineNoHeight
2023-02-06 09:30:23 +01:00
rtn.Renderer = renderer
2022-06-13 20:11:56 +02:00
return rtn
}
2023-03-21 03:20:57 +01:00
func makeNewLineText(screenId string, userId string, text string) *LineType {
2022-06-13 20:11:56 +02:00
rtn := &LineType{}
2023-03-15 00:37:22 +01:00
rtn.ScreenId = screenId
rtn.UserId = userId
rtn.LineId = scbase.GenPromptUUID()
2022-06-13 20:11:56 +02:00
rtn.Ts = time.Now().UnixMilli()
rtn.LineLocal = true
2022-06-13 20:11:56 +02:00
rtn.LineType = LineTypeText
rtn.Text = text
rtn.ContentHeight = LineNoHeight
2022-06-13 20:11:56 +02:00
return rtn
}
2022-06-21 06:57:23 +02:00
2023-03-21 03:20:57 +01:00
func AddCommentLine(ctx context.Context, screenId string, userId string, commentText string) (*LineType, error) {
rtnLine := makeNewLineText(screenId, userId, commentText)
err := InsertLine(ctx, rtnLine, nil)
2022-07-05 19:51:47 +02:00
if err != nil {
return nil, err
}
return rtnLine, nil
}
2023-03-21 03:20:57 +01:00
func AddCmdLine(ctx context.Context, screenId string, userId string, cmd *CmdType, renderer string) (*LineType, error) {
rtnLine := makeNewLineCmd(screenId, userId, cmd.CmdId, renderer)
err := InsertLine(ctx, rtnLine, cmd)
2022-07-05 19:51:47 +02:00
if err != nil {
return nil, err
}
return rtnLine, nil
}
2022-07-01 21:17:19 +02:00
func EnsureLocalRemote(ctx context.Context) error {
physicalId, err := base.GetRemoteId()
2022-07-01 21:17:19 +02:00
if err != nil {
return fmt.Errorf("getting local physical remoteid: %w", err)
2022-07-01 21:17:19 +02:00
}
remote, err := GetLocalRemote(ctx)
2022-07-01 21:17:19 +02:00
if err != nil {
return fmt.Errorf("getting local remote from db: %w", err)
2022-07-01 21:17:19 +02:00
}
if remote != nil {
return nil
}
2022-08-17 00:08:28 +02:00
hostName, err := os.Hostname()
if err != nil {
return fmt.Errorf("getting hostname: %w", err)
}
user, err := user.Current()
if err != nil {
return fmt.Errorf("getting user: %w", err)
}
2022-07-01 21:17:19 +02:00
// create the local remote
localRemote := &RemoteType{
RemoteId: scbase.GenPromptUUID(),
PhysicalId: physicalId,
RemoteType: RemoteTypeSsh,
RemoteAlias: LocalRemoteAlias,
2022-08-17 00:08:28 +02:00
RemoteCanonicalName: fmt.Sprintf("%s@%s", user.Username, hostName),
RemoteSudo: false,
RemoteUser: user.Username,
RemoteHost: hostName,
ConnectMode: ConnectModeStartup,
2022-09-25 07:42:52 +02:00
AutoInstall: true,
2022-08-24 06:05:49 +02:00
SSHOpts: &SSHOpts{Local: true},
Local: true,
2022-07-01 21:17:19 +02:00
}
2022-09-14 02:11:36 +02:00
err = UpsertRemote(ctx, localRemote)
2022-07-01 21:17:19 +02:00
if err != nil {
return err
}
log.Printf("[db] added local remote '%s', id=%s\n", localRemote.RemoteCanonicalName, localRemote.RemoteId)
2022-12-29 09:07:16 +01:00
sudoRemote := &RemoteType{
RemoteId: scbase.GenPromptUUID(),
PhysicalId: "",
RemoteType: RemoteTypeSsh,
RemoteAlias: "sudo",
RemoteCanonicalName: fmt.Sprintf("sudo@%s@%s", user.Username, hostName),
RemoteSudo: true,
RemoteUser: "root",
RemoteHost: hostName,
ConnectMode: ConnectModeManual,
AutoInstall: true,
SSHOpts: &SSHOpts{Local: true},
RemoteOpts: &RemoteOptsType{Color: "red"},
Local: true,
}
err = UpsertRemote(ctx, sudoRemote)
if err != nil {
return err
}
log.Printf("[db] added sudo remote '%s', id=%s\n", sudoRemote.RemoteCanonicalName, sudoRemote.RemoteId)
return nil
}
2022-07-01 23:45:33 +02:00
func EnsureDefaultSession(ctx context.Context) (*SessionType, error) {
2022-07-01 23:07:13 +02:00
session, err := GetSessionByName(ctx, DefaultSessionName)
if err != nil {
2022-07-01 23:45:33 +02:00
return nil, err
2022-07-01 23:07:13 +02:00
}
if session != nil {
2022-07-01 23:45:33 +02:00
return session, nil
2022-07-01 23:07:13 +02:00
}
_, err = InsertSessionWithName(ctx, DefaultSessionName, ShareModeLocal, true)
2022-07-01 23:07:13 +02:00
if err != nil {
2022-07-01 23:45:33 +02:00
return nil, err
2022-07-01 23:07:13 +02:00
}
2022-07-01 23:45:33 +02:00
return GetSessionByName(ctx, DefaultSessionName)
2022-07-01 21:17:19 +02:00
}
func createClientData(tx *TxWrap) error {
curve := elliptic.P384()
pkey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return fmt.Errorf("generating P-834 key: %w", err)
}
pkBytes, err := x509.MarshalECPrivateKey(pkey)
if err != nil {
return fmt.Errorf("marshaling (pkcs8) private key bytes: %w", err)
}
pubBytes, err := x509.MarshalPKIXPublicKey(&pkey.PublicKey)
if err != nil {
return fmt.Errorf("marshaling (pkix) public key bytes: %w", err)
}
2022-09-22 07:02:38 +02:00
c := ClientData{
ClientId: uuid.New().String(),
UserId: uuid.New().String(),
UserPrivateKeyBytes: pkBytes,
UserPublicKeyBytes: pubBytes,
ActiveSessionId: "",
2022-09-25 09:26:33 +02:00
WinSize: ClientWinSizeType{},
2023-03-21 03:20:57 +01:00
CmdStoreType: CmdStoreTypeScreen,
2022-09-22 07:02:38 +02:00
}
2022-09-25 09:26:33 +02:00
query := `INSERT INTO client ( clientid, userid, activesessionid, userpublickeybytes, userprivatekeybytes, winsize)
VALUES (:clientid,:userid,:activesessionid,:userpublickeybytes,:userprivatekeybytes,:winsize)`
tx.NamedExec(query, dbutil.ToDBMap(c, false))
2022-10-31 20:40:45 +01:00
log.Printf("create new clientid[%s] userid[%s] with public/private keypair\n", c.ClientId, c.UserId)
return nil
}
func EnsureClientData(ctx context.Context) (*ClientData, error) {
rtn, err := WithTxRtn(ctx, func(tx *TxWrap) (*ClientData, error) {
query := `SELECT count(*) FROM client`
count := tx.GetInt(query)
if count > 1 {
return nil, fmt.Errorf("invalid client database, multiple (%d) rows in client table", count)
}
if count == 0 {
createErr := createClientData(tx)
if createErr != nil {
return nil, createErr
}
}
cdata := dbutil.GetMappable[*ClientData](tx, `SELECT * FROM client`)
2022-09-22 07:02:38 +02:00
if cdata == nil {
return nil, fmt.Errorf("no client data found")
}
dbVersion := tx.GetInt(`SELECT version FROM schema_migrations`)
cdata.DBVersion = dbVersion
return cdata, nil
})
if err != nil {
return nil, err
}
if rtn.UserId == "" {
return nil, fmt.Errorf("invalid client data (no userid)")
}
if len(rtn.UserPrivateKeyBytes) == 0 || len(rtn.UserPublicKeyBytes) == 0 {
return nil, fmt.Errorf("invalid client data (no public/private keypair)")
}
rtn.UserPrivateKey, err = x509.ParseECPrivateKey(rtn.UserPrivateKeyBytes)
if err != nil {
return nil, fmt.Errorf("invalid client data, cannot parse private key: %w", err)
}
pubKey, err := x509.ParsePKIXPublicKey(rtn.UserPublicKeyBytes)
if err != nil {
return nil, fmt.Errorf("invalid client data, cannot parse public key: %w", err)
}
var ok bool
rtn.UserPublicKey, ok = pubKey.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid client data, wrong public key type: %T", pubKey)
}
return rtn, nil
}
2023-03-21 03:20:57 +01:00
func GetCmdMigrationInfo(ctx context.Context) (*ClientMigrationData, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*ClientMigrationData, error) {
cdata := dbutil.GetMappable[*ClientData](tx, `SELECT * FROM client`)
2023-03-21 03:20:57 +01:00
if cdata == nil {
return nil, fmt.Errorf("no client data found")
}
if cdata.CmdStoreType == "session" {
total := tx.GetInt(`SELECT count(*) FROM cmd`)
posInv := tx.GetInt(`SELECT count(*) FROM cmd_migrate`)
mdata := &ClientMigrationData{
MigrationType: "cmdscreen",
MigrationPos: total - posInv,
MigrationTotal: total,
MigrationDone: false,
}
return mdata, nil
}
// no migration info
return nil, nil
})
}
func SetClientOpts(ctx context.Context, clientOpts ClientOptsType) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE client SET clientopts = ?`
tx.Exec(query, quickJson(clientOpts))
return nil
})
return txErr
}
2023-03-21 03:20:57 +01:00
type cmdMigrationType struct {
SessionId string
ScreenId string
CmdId string
}
func getSliceChunk[T any](slice []T, chunkSize int) ([]T, []T) {
if chunkSize >= len(slice) {
return slice, nil
}
return slice[0:chunkSize], slice[chunkSize:]
}
func processChunk(ctx context.Context, mchunk []cmdMigrationType) error {
for _, mig := range mchunk {
newFile, err := scbase.PtyOutFile(mig.ScreenId, mig.CmdId)
if err != nil {
log.Printf("ptyoutfile error: %v\n", err)
continue
}
oldFile, err := scbase.PtyOutFile_Sessions(mig.SessionId, mig.CmdId)
if err != nil {
log.Printf("ptyoutfile_sessions error: %v\n", err)
continue
}
err = os.Rename(oldFile, newFile)
if err != nil {
log.Printf("error renaming %s => %s: %v\n", oldFile, newFile, err)
continue
}
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
for _, mig := range mchunk {
query := `DELETE FROM cmd_migrate WHERE cmdid = ?`
tx.Exec(query, mig.CmdId)
}
return nil
})
if txErr != nil {
return txErr
}
return nil
}
func RunCmdScreenMigration() {
ctx := context.Background()
startTime := time.Now()
mdata, err := GetCmdMigrationInfo(ctx)
if err != nil {
log.Printf("[prompt] error trying to run cmd migration: %v\n", err)
return
}
if mdata == nil || mdata.MigrationType != "cmdscreen" {
return
}
var migrations []cmdMigrationType
txErr := WithTx(ctx, func(tx *TxWrap) error {
tx.Select(&migrations, `SELECT * FROM cmd_migrate`)
return nil
})
if txErr != nil {
log.Printf("[prompt] error trying to get cmd migrations: %v\n", txErr)
return
}
log.Printf("[db] got %d cmd migrations\n", len(migrations))
for len(migrations) > 0 {
var mchunk []cmdMigrationType
mchunk, migrations = getSliceChunk(migrations, 5)
err = processChunk(ctx, mchunk)
if err != nil {
log.Printf("[prompt] cmd migration failed on chunk: %v\n%#v\n", err, mchunk)
return
}
}
err = os.RemoveAll(scbase.GetSessionsDir())
if err != nil {
log.Printf("[db] cannot remove old sessions dir %s: %v\n", scbase.GetSessionsDir(), err)
}
txErr = WithTx(ctx, func(tx *TxWrap) error {
query := `UPDATE client SET cmdstoretype = 'screen'`
tx.Exec(query)
return nil
})
if txErr != nil {
log.Printf("[db] cannot change client cmdstoretype: %v\n", err)
}
log.Printf("[db] cmd screen migration done: %v\n", time.Since(startTime))
return
}