Files

408 lines
9.8 KiB
Go
Raw Permalink Normal View History

2023-10-17 12:31:13 +08:00
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
2022-06-30 17:02:19 -07:00
package scbase
import (
2023-02-23 15:17:47 -08:00
"context"
"errors"
"fmt"
"io"
"io/fs"
2022-10-31 12:40:45 -07:00
"log"
2022-06-30 17:02:19 -07:00
"os"
2023-02-23 15:17:47 -08:00
"os/exec"
2022-06-30 17:02:19 -07:00
"path"
2023-02-23 15:17:47 -08:00
"regexp"
"runtime"
2022-09-20 17:37:49 -07:00
"strconv"
2023-02-23 15:17:47 -08:00
"strings"
"sync"
2023-02-23 15:17:47 -08:00
"time"
2023-07-30 17:16:43 -07:00
"github.com/google/uuid"
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
"golang.org/x/mod/semver"
"golang.org/x/sys/unix"
2022-06-30 17:02:19 -07:00
)
const HomeVarName = "HOME"
const WaveHomeVarName = "WAVETERM_HOME"
const WaveDevVarName = "WAVETERM_DEV"
const SessionsDirBaseName = "sessions"
2023-03-20 19:20:57 -07:00
const ScreensDirBaseName = "screens"
const WaveLockFile = "waveterm.lock"
const WaveDirName = ".waveterm" // must match emain.ts
const WaveDevDirName = ".waveterm-dev" // must match emain.ts
const WaveAppPathVarName = "WAVETERM_APP_PATH"
2024-01-29 09:39:36 -08:00
const WaveVersion = "v0.6.1"
const WaveAuthKeyFileName = "waveterm.authkey"
2024-01-16 16:11:04 -08:00
const MShellVersion = "v0.4.0"
var SessionDirCache = make(map[string]string)
2023-03-20 19:20:57 -07:00
var ScreenDirCache = make(map[string]string)
var BaseLock = &sync.Mutex{}
2023-02-23 15:17:47 -08:00
var BuildTime = "-"
2022-06-30 17:02:19 -07:00
func IsDevMode() bool {
pdev := os.Getenv(WaveDevVarName)
return pdev != ""
}
// must match js
func GetWaveHomeDir() string {
scHome := os.Getenv(WaveHomeVarName)
2022-06-30 17:02:19 -07:00
if scHome == "" {
homeVar := os.Getenv(HomeVarName)
if homeVar == "" {
homeVar = "/"
}
pdev := os.Getenv(WaveDevVarName)
2022-12-28 13:56:19 -08:00
if pdev != "" {
scHome = path.Join(homeVar, WaveDevDirName)
2022-12-28 13:56:19 -08:00
} else {
scHome = path.Join(homeVar, WaveDirName)
2022-12-28 13:56:19 -08:00
}
2022-06-30 17:02:19 -07:00
}
return scHome
}
func MShellBinaryDir() string {
appPath := os.Getenv(WaveAppPathVarName)
if appPath == "" {
appPath = "."
}
return path.Join(appPath, "bin", "mshell")
}
func MShellBinaryPath(version string, goos string, goarch string) (string, error) {
if !base.ValidGoArch(goos, goarch) {
return "", fmt.Errorf("invalid goos/goarch combination: %s/%s", goos, goarch)
}
binaryDir := MShellBinaryDir()
versionStr := semver.MajorMinor(version)
if versionStr == "" {
return "", fmt.Errorf("invalid mshell version: %q", version)
}
fileName := fmt.Sprintf("mshell-%s-%s.%s", versionStr, goos, goarch)
fullFileName := path.Join(binaryDir, fileName)
return fullFileName, nil
}
func LocalMShellBinaryPath() (string, error) {
return MShellBinaryPath(MShellVersion, runtime.GOOS, runtime.GOARCH)
}
func MShellBinaryReader(version string, goos string, goarch string) (io.ReadCloser, error) {
mshellPath, err := MShellBinaryPath(version, goos, goarch)
if err != nil {
return nil, err
}
fd, err := os.Open(mshellPath)
if err != nil {
return nil, fmt.Errorf("cannot open mshell binary %q: %v", mshellPath, err)
}
return fd, nil
}
func createWaveAuthKeyFile(fileName string) (string, error) {
fd, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return "", err
}
defer fd.Close()
keyStr := GenWaveUUID()
_, err = fd.Write([]byte(keyStr))
if err != nil {
return "", err
}
return keyStr, nil
}
func ReadWaveAuthKey() (string, error) {
homeDir := GetWaveHomeDir()
err := ensureDir(homeDir)
if err != nil {
return "", fmt.Errorf("cannot find/create WAVETERM_HOME directory %q", homeDir)
}
fileName := path.Join(homeDir, WaveAuthKeyFileName)
fd, err := os.Open(fileName)
if err != nil && errors.Is(err, fs.ErrNotExist) {
return createWaveAuthKeyFile(fileName)
}
if err != nil {
return "", fmt.Errorf("error opening wave authkey:%s: %v", fileName, err)
}
defer fd.Close()
buf, err := io.ReadAll(fd)
if err != nil {
return "", fmt.Errorf("error reading wave authkey:%s: %v", fileName, err)
}
keyStr := string(buf)
_, err = uuid.Parse(keyStr)
if err != nil {
return "", fmt.Errorf("invalid authkey:%s format: %v", fileName, err)
}
return keyStr, nil
}
func AcquireWaveLock() (*os.File, error) {
homeDir := GetWaveHomeDir()
2022-10-31 12:40:45 -07:00
err := ensureDir(homeDir)
if err != nil {
return nil, fmt.Errorf("cannot find/create WAVETERM_HOME directory %q", homeDir)
2022-10-31 12:40:45 -07:00
}
lockFileName := path.Join(homeDir, WaveLockFile)
log.Printf("[base] acquiring lock on %s\n", lockFileName)
fd, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return nil, err
}
err = unix.Flock(int(fd.Fd()), unix.LOCK_EX|unix.LOCK_NB)
if err != nil {
fd.Close()
return nil, err
}
return fd, nil
}
2023-03-20 19:20:57 -07:00
// deprecated (v0.1.8)
func EnsureSessionDir(sessionId string) (string, error) {
2022-08-24 13:21:54 -07:00
if sessionId == "" {
return "", fmt.Errorf("cannot get session dir for blank sessionid")
}
BaseLock.Lock()
sdir, ok := SessionDirCache[sessionId]
BaseLock.Unlock()
if ok {
return sdir, nil
}
scHome := GetWaveHomeDir()
sdir = path.Join(scHome, SessionsDirBaseName, sessionId)
err := ensureDir(sdir)
if err != nil {
return "", err
}
BaseLock.Lock()
SessionDirCache[sessionId] = sdir
BaseLock.Unlock()
return sdir, nil
}
2023-03-20 19:20:57 -07:00
// deprecated (v0.1.8)
2022-12-26 18:49:45 -08:00
func GetSessionsDir() string {
waveHome := GetWaveHomeDir()
sdir := path.Join(waveHome, SessionsDirBaseName)
2022-12-26 18:49:45 -08:00
return sdir
}
2023-03-20 19:20:57 -07:00
func EnsureScreenDir(screenId string) (string, error) {
if screenId == "" {
return "", fmt.Errorf("cannot get screen dir for blank sessionid")
}
BaseLock.Lock()
sdir, ok := ScreenDirCache[screenId]
BaseLock.Unlock()
if ok {
return sdir, nil
}
scHome := GetWaveHomeDir()
2023-03-20 19:20:57 -07:00
sdir = path.Join(scHome, ScreensDirBaseName, screenId)
err := ensureDir(sdir)
if err != nil {
return "", err
}
BaseLock.Lock()
ScreenDirCache[screenId] = sdir
BaseLock.Unlock()
return sdir, nil
}
func GetScreensDir() string {
waveHome := GetWaveHomeDir()
sdir := path.Join(waveHome, ScreensDirBaseName)
2023-03-20 19:20:57 -07:00
return sdir
}
func ensureDir(dirName string) error {
info, err := os.Stat(dirName)
if errors.Is(err, fs.ErrNotExist) {
err = os.MkdirAll(dirName, 0700)
if err != nil {
return err
}
log.Printf("[wave] created directory %q\n", dirName)
info, err = os.Stat(dirName)
}
if err != nil {
return err
}
if !info.IsDir() {
return fmt.Errorf("'%s' must be a directory", dirName)
}
return nil
}
2023-03-20 19:20:57 -07:00
// deprecated (v0.1.8)
func PtyOutFile_Sessions(sessionId string, cmdId string) (string, error) {
sdir, err := EnsureSessionDir(sessionId)
if err != nil {
return "", err
}
2022-08-24 13:21:54 -07:00
if sessionId == "" {
return "", fmt.Errorf("cannot get ptyout file for blank sessionid")
}
if cmdId == "" {
return "", fmt.Errorf("cannot get ptyout file for blank cmdid")
}
return fmt.Sprintf("%s/%s.ptyout.cf", sdir, cmdId), nil
}
2023-07-30 17:16:43 -07:00
func PtyOutFile(screenId string, lineId string) (string, error) {
2023-03-20 19:20:57 -07:00
sdir, err := EnsureScreenDir(screenId)
if err != nil {
return "", err
}
2023-03-20 19:20:57 -07:00
if screenId == "" {
return "", fmt.Errorf("cannot get ptyout file for blank screenid")
2022-08-24 13:21:54 -07:00
}
2023-07-30 17:16:43 -07:00
if lineId == "" {
return "", fmt.Errorf("cannot get ptyout file for blank lineid")
2022-08-24 13:21:54 -07:00
}
2023-07-30 17:16:43 -07:00
return fmt.Sprintf("%s/%s.ptyout.cf", sdir, lineId), nil
}
2022-09-20 17:37:49 -07:00
func GenWaveUUID() string {
2022-09-20 17:37:49 -07:00
for {
rtn := uuid.New().String()
_, err := strconv.Atoi(rtn[0:8])
if err == nil { // do not allow UUIDs where the initial 8 bytes parse to an integer
continue
}
return rtn
}
}
func NumFormatDec(num int64) string {
var signStr string
absNum := num
if absNum < 0 {
absNum = -absNum
signStr = "-"
}
if absNum < 1000 {
// raw num
return signStr + strconv.FormatInt(absNum, 10)
}
if absNum < 1000000 {
// k num
kVal := float64(absNum) / 1000
return signStr + strconv.FormatFloat(kVal, 'f', 2, 64) + "k"
}
if absNum < 1000000000 {
// M num
mVal := float64(absNum) / 1000000
return signStr + strconv.FormatFloat(mVal, 'f', 2, 64) + "m"
} else {
// G num
gVal := float64(absNum) / 1000000000
return signStr + strconv.FormatFloat(gVal, 'f', 2, 64) + "g"
}
}
func NumFormatB2(num int64) string {
var signStr string
absNum := num
if absNum < 0 {
absNum = -absNum
signStr = "-"
}
if absNum < 1024 {
// raw num
return signStr + strconv.FormatInt(absNum, 10)
}
if absNum < 1000000 {
// k num
if absNum%1024 == 0 {
return signStr + strconv.FormatInt(absNum/1024, 10) + "K"
}
kVal := float64(absNum) / 1024
return signStr + strconv.FormatFloat(kVal, 'f', 2, 64) + "K"
}
if absNum < 1000000000 {
// M num
if absNum%(1024*1024) == 0 {
return signStr + strconv.FormatInt(absNum/(1024*1024), 10) + "M"
}
mVal := float64(absNum) / (1024 * 1024)
return signStr + strconv.FormatFloat(mVal, 'f', 2, 64) + "M"
} else {
// G num
if absNum%(1024*1024*1024) == 0 {
return signStr + strconv.FormatInt(absNum/(1024*1024*1024), 10) + "G"
}
gVal := float64(absNum) / (1024 * 1024 * 1024)
return signStr + strconv.FormatFloat(gVal, 'f', 2, 64) + "G"
}
}
2023-01-16 23:36:52 -08:00
func ClientArch() string {
return fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
}
2023-02-23 15:17:47 -08:00
2024-01-23 17:19:03 -08:00
var releaseRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
2023-02-23 15:17:47 -08:00
var osReleaseOnce = &sync.Once{}
var osRelease string
2024-01-23 17:19:03 -08:00
func unameKernelRelease() string {
2023-02-23 15:17:47 -08:00
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
out, err := exec.CommandContext(ctx, "uname", "-r").CombinedOutput()
if err != nil {
log.Printf("error executing uname -r: %v\n", err)
return "-"
}
releaseStr := strings.TrimSpace(string(out))
2024-01-23 17:19:03 -08:00
m := releaseRegex.FindStringSubmatch(releaseStr)
if m == nil || len(m) < 2 {
2023-02-23 15:17:47 -08:00
log.Printf("invalid uname -r output: [%s]\n", releaseStr)
return "-"
}
2024-01-23 17:19:03 -08:00
return m[1]
2023-02-23 15:17:47 -08:00
}
2024-01-23 17:19:03 -08:00
func UnameKernelRelease() string {
2023-02-23 15:17:47 -08:00
osReleaseOnce.Do(func() {
2024-01-23 17:19:03 -08:00
osRelease = unameKernelRelease()
2023-02-23 15:17:47 -08:00
})
return osRelease
}
2024-01-08 18:31:17 -08:00
var osLangOnce = &sync.Once{}
var osLang string
func determineLang() string {
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
if runtime.GOOS == "darwin" {
out, err := exec.CommandContext(ctx, "defaults", "read", "-g", "AppleLocale").CombinedOutput()
if err != nil {
log.Printf("error executing 'defaults read -g AppleLocale': %v\n", err)
return ""
}
return strings.TrimSpace(string(out)) + ".UTF-8"
} else {
// this is specifically to get the wavesrv LANG so waveshell
// on a remote uses the same LANG
return os.Getenv("LANG")
}
}
func DetermineLang() string {
osLangOnce.Do(func() {
osLang = determineLang()
})
return osLang
}