2024-05-14 22:34:41 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2024-05-12 18:52:12 +02:00
|
|
|
package wavebase
|
|
|
|
|
|
|
|
import (
|
2024-05-16 09:29:58 +02:00
|
|
|
"context"
|
2024-05-12 18:52:12 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
2024-05-16 09:29:58 +02:00
|
|
|
"log"
|
2024-05-12 18:52:12 +02:00
|
|
|
"os"
|
2024-05-16 09:29:58 +02:00
|
|
|
"os/exec"
|
2024-05-21 00:28:47 +02:00
|
|
|
"path/filepath"
|
2024-08-09 03:24:54 +02:00
|
|
|
"regexp"
|
2024-05-16 09:29:58 +02:00
|
|
|
"runtime"
|
2024-05-12 18:52:12 +02:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2024-05-16 09:29:58 +02:00
|
|
|
"time"
|
2024-05-12 18:52:12 +02:00
|
|
|
)
|
|
|
|
|
2024-08-01 08:47:33 +02:00
|
|
|
// set by main-server.go
|
|
|
|
var WaveVersion = "0.0.0"
|
2024-08-09 03:24:54 +02:00
|
|
|
var BuildTime = "0"
|
2024-08-01 08:47:33 +02:00
|
|
|
|
2024-10-27 21:12:41 +01:00
|
|
|
const (
|
|
|
|
WaveConfigHomeEnvVar = "WAVETERM_CONFIG_HOME"
|
|
|
|
WaveDataHomeEnvVar = "WAVETERM_DATA_HOME"
|
|
|
|
WaveAppPathVarName = "WAVETERM_APP_PATH"
|
|
|
|
WaveDevVarName = "WAVETERM_DEV"
|
|
|
|
WaveDevViteVarName = "WAVETERM_DEV_VITE"
|
|
|
|
)
|
|
|
|
|
|
|
|
var ConfigHome_VarCache string // caches WAVETERM_CONFIG_HOME
|
|
|
|
var DataHome_VarCache string // caches WAVETERM_DATA_HOME
|
|
|
|
var AppPath_VarCache string // caches WAVETERM_APP_PATH
|
|
|
|
var Dev_VarCache string // caches WAVETERM_DEV
|
|
|
|
|
2024-09-05 23:05:42 +02:00
|
|
|
const WaveLockFile = "wave.lock"
|
2024-07-18 00:24:43 +02:00
|
|
|
const DomainSocketBaseName = "wave.sock"
|
2024-10-24 07:43:17 +02:00
|
|
|
const RemoteDomainSocketBaseName = "wave-remote.sock"
|
2024-09-05 23:05:42 +02:00
|
|
|
const WaveDBDir = "db"
|
2024-07-26 01:41:34 +02:00
|
|
|
const JwtSecret = "waveterm" // TODO generate and store this
|
2024-09-11 18:26:43 +02:00
|
|
|
const ConfigDir = "config"
|
2024-05-12 18:52:12 +02:00
|
|
|
|
2024-10-24 07:43:17 +02:00
|
|
|
var RemoteWaveHome = ExpandHomeDirSafe("~/.waveterm")
|
|
|
|
|
2024-10-04 05:28:05 +02:00
|
|
|
const AppPathBinDir = "bin"
|
|
|
|
|
2024-05-12 18:52:12 +02:00
|
|
|
var baseLock = &sync.Mutex{}
|
|
|
|
var ensureDirCache = map[string]bool{}
|
|
|
|
|
2024-10-04 21:20:52 +02:00
|
|
|
type FDLock interface {
|
|
|
|
Close() error
|
|
|
|
}
|
|
|
|
|
2024-10-27 21:12:41 +01:00
|
|
|
func CacheAndRemoveEnvVars() error {
|
|
|
|
ConfigHome_VarCache = os.Getenv(WaveConfigHomeEnvVar)
|
|
|
|
if ConfigHome_VarCache == "" {
|
|
|
|
return fmt.Errorf(WaveConfigHomeEnvVar + " not set")
|
|
|
|
}
|
|
|
|
os.Unsetenv(WaveConfigHomeEnvVar)
|
|
|
|
DataHome_VarCache = os.Getenv(WaveDataHomeEnvVar)
|
|
|
|
if DataHome_VarCache == "" {
|
|
|
|
return fmt.Errorf("%s not set", WaveDataHomeEnvVar)
|
|
|
|
}
|
|
|
|
os.Unsetenv(WaveDataHomeEnvVar)
|
|
|
|
AppPath_VarCache = os.Getenv(WaveAppPathVarName)
|
|
|
|
os.Unsetenv(WaveAppPathVarName)
|
|
|
|
Dev_VarCache = os.Getenv(WaveDevVarName)
|
|
|
|
os.Unsetenv(WaveDevVarName)
|
|
|
|
os.Unsetenv(WaveDevViteVarName)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-12 18:52:12 +02:00
|
|
|
func IsDevMode() bool {
|
2024-10-27 21:12:41 +01:00
|
|
|
return Dev_VarCache != ""
|
2024-05-12 18:52:12 +02:00
|
|
|
}
|
|
|
|
|
2024-10-04 05:28:05 +02:00
|
|
|
func GetWaveAppPath() string {
|
2024-10-27 21:12:41 +01:00
|
|
|
return AppPath_VarCache
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetWaveDataDir() string {
|
|
|
|
return DataHome_VarCache
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetWaveConfigDir() string {
|
|
|
|
return ConfigHome_VarCache
|
2024-10-04 05:28:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetWaveAppBinPath() string {
|
|
|
|
return filepath.Join(GetWaveAppPath(), AppPathBinDir)
|
|
|
|
}
|
|
|
|
|
2024-05-12 18:52:12 +02:00
|
|
|
func GetHomeDir() string {
|
2024-06-15 23:59:14 +02:00
|
|
|
homeVar, err := os.UserHomeDir()
|
|
|
|
if err != nil {
|
2024-05-12 18:52:12 +02:00
|
|
|
return "/"
|
|
|
|
}
|
|
|
|
return homeVar
|
|
|
|
}
|
|
|
|
|
2024-09-25 03:24:39 +02:00
|
|
|
func ExpandHomeDir(pathStr string) (string, error) {
|
2024-09-30 23:50:40 +02:00
|
|
|
if pathStr != "~" && !strings.HasPrefix(pathStr, "~/") && (!strings.HasPrefix(pathStr, `~\`) || runtime.GOOS != "windows") {
|
2024-09-25 21:58:30 +02:00
|
|
|
return filepath.Clean(pathStr), nil
|
2024-05-12 18:52:12 +02:00
|
|
|
}
|
|
|
|
homeDir := GetHomeDir()
|
|
|
|
if pathStr == "~" {
|
2024-09-25 03:24:39 +02:00
|
|
|
return homeDir, nil
|
2024-05-12 18:52:12 +02:00
|
|
|
}
|
2024-09-25 21:58:30 +02:00
|
|
|
expandedPath := filepath.Clean(filepath.Join(homeDir, pathStr[2:]))
|
2024-09-25 03:24:39 +02:00
|
|
|
absPath, err := filepath.Abs(filepath.Join(homeDir, expandedPath))
|
|
|
|
if err != nil || !strings.HasPrefix(absPath, homeDir) {
|
2024-09-25 21:58:30 +02:00
|
|
|
return "", fmt.Errorf("potential path traversal detected for path %s", pathStr)
|
2024-09-25 03:24:39 +02:00
|
|
|
}
|
|
|
|
return expandedPath, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ExpandHomeDirSafe(pathStr string) string {
|
|
|
|
path, _ := ExpandHomeDir(pathStr)
|
|
|
|
return path
|
2024-05-12 18:52:12 +02:00
|
|
|
}
|
|
|
|
|
2024-05-16 09:29:58 +02:00
|
|
|
func ReplaceHomeDir(pathStr string) string {
|
|
|
|
homeDir := GetHomeDir()
|
|
|
|
if pathStr == homeDir {
|
|
|
|
return "~"
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(pathStr, homeDir+"/") {
|
|
|
|
return "~" + pathStr[len(homeDir):]
|
|
|
|
}
|
|
|
|
return pathStr
|
|
|
|
}
|
|
|
|
|
2024-07-18 00:24:43 +02:00
|
|
|
func GetDomainSocketName() string {
|
Update data and config paths to match platform defaults (#1047)
Going forward for new installations, config and data files will be
stored at the platform default paths, as defined by
[env-paths](https://www.npmjs.com/package/env-paths).
For backwards compatibility, if the `~/.waveterm` or `WAVETERM_HOME`
directory exists and contains valid data, it will be used. If this check
fails, then `WAVETERM_DATA_HOME` and `WAVETERM_CONFIG_HOME` will be
used. If these are not defined, then `XDG_DATA_HOME` and
`XDG_CONFIG_HOME` will be used. Finally, if none of these are defined,
the [env-paths](https://www.npmjs.com/package/env-paths) defaults will
be used.
As with the existing app, dev instances will write to `waveterm-dev`
directories, while all others will write to `waveterm`.
2024-10-22 18:26:58 +02:00
|
|
|
return filepath.Join(GetWaveDataDir(), DomainSocketBaseName)
|
2024-07-18 00:24:43 +02:00
|
|
|
}
|
|
|
|
|
2024-10-24 07:43:17 +02:00
|
|
|
func GetRemoteDomainSocketName() string {
|
|
|
|
return filepath.Join(RemoteWaveHome, RemoteDomainSocketBaseName)
|
|
|
|
}
|
|
|
|
|
Update data and config paths to match platform defaults (#1047)
Going forward for new installations, config and data files will be
stored at the platform default paths, as defined by
[env-paths](https://www.npmjs.com/package/env-paths).
For backwards compatibility, if the `~/.waveterm` or `WAVETERM_HOME`
directory exists and contains valid data, it will be used. If this check
fails, then `WAVETERM_DATA_HOME` and `WAVETERM_CONFIG_HOME` will be
used. If these are not defined, then `XDG_DATA_HOME` and
`XDG_CONFIG_HOME` will be used. Finally, if none of these are defined,
the [env-paths](https://www.npmjs.com/package/env-paths) defaults will
be used.
As with the existing app, dev instances will write to `waveterm-dev`
directories, while all others will write to `waveterm`.
2024-10-22 18:26:58 +02:00
|
|
|
func EnsureWaveDataDir() error {
|
|
|
|
return CacheEnsureDir(GetWaveDataDir(), "wavehome", 0700, "wave home directory")
|
2024-05-12 18:52:12 +02:00
|
|
|
}
|
|
|
|
|
2024-09-05 23:05:42 +02:00
|
|
|
func EnsureWaveDBDir() error {
|
Update data and config paths to match platform defaults (#1047)
Going forward for new installations, config and data files will be
stored at the platform default paths, as defined by
[env-paths](https://www.npmjs.com/package/env-paths).
For backwards compatibility, if the `~/.waveterm` or `WAVETERM_HOME`
directory exists and contains valid data, it will be used. If this check
fails, then `WAVETERM_DATA_HOME` and `WAVETERM_CONFIG_HOME` will be
used. If these are not defined, then `XDG_DATA_HOME` and
`XDG_CONFIG_HOME` will be used. Finally, if none of these are defined,
the [env-paths](https://www.npmjs.com/package/env-paths) defaults will
be used.
As with the existing app, dev instances will write to `waveterm-dev`
directories, while all others will write to `waveterm`.
2024-10-22 18:26:58 +02:00
|
|
|
return CacheEnsureDir(filepath.Join(GetWaveDataDir(), WaveDBDir), "wavedb", 0700, "wave db directory")
|
|
|
|
}
|
|
|
|
|
|
|
|
func EnsureWaveConfigDir() error {
|
|
|
|
return CacheEnsureDir(GetWaveConfigDir(), "waveconfig", 0700, "wave config directory")
|
|
|
|
}
|
|
|
|
|
|
|
|
func EnsureWavePresetsDir() error {
|
|
|
|
return CacheEnsureDir(filepath.Join(GetWaveConfigDir(), "presets"), "wavepresets", 0700, "wave presets directory")
|
2024-09-05 23:05:42 +02:00
|
|
|
}
|
|
|
|
|
2024-05-12 18:52:12 +02:00
|
|
|
func CacheEnsureDir(dirName string, cacheKey string, perm os.FileMode, dirDesc string) error {
|
|
|
|
baseLock.Lock()
|
|
|
|
ok := ensureDirCache[cacheKey]
|
|
|
|
baseLock.Unlock()
|
|
|
|
if ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
err := TryMkdirs(dirName, perm, dirDesc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
baseLock.Lock()
|
|
|
|
ensureDirCache[cacheKey] = true
|
|
|
|
baseLock.Unlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TryMkdirs(dirName string, perm os.FileMode, dirDesc string) error {
|
|
|
|
info, err := os.Stat(dirName)
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
err = os.MkdirAll(dirName, perm)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot make %s %q: %w", dirDesc, dirName, err)
|
|
|
|
}
|
|
|
|
info, err = os.Stat(dirName)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error trying to stat %s: %w", dirDesc, err)
|
|
|
|
}
|
|
|
|
if !info.IsDir() {
|
|
|
|
return fmt.Errorf("%s %q must be a directory", dirDesc, dirName)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2024-05-16 09:29:58 +02: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 ""
|
|
|
|
}
|
|
|
|
strOut := string(out)
|
|
|
|
truncOut := strings.Split(strOut, "@")[0]
|
|
|
|
return strings.TrimSpace(truncOut) + ".UTF-8"
|
2024-06-22 09:41:49 +02:00
|
|
|
} else if runtime.GOOS == "win32" {
|
|
|
|
out, err := exec.CommandContext(ctx, "Get-Culture", "|", "select", "-exp", "Name").CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error executing 'Get-Culture | select -exp Name': %v\n", err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(string(out)) + ".UTF-8"
|
2024-05-16 09:29:58 +02:00
|
|
|
} 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
|
|
|
|
}
|
2024-05-21 00:28:47 +02:00
|
|
|
|
2024-06-22 09:41:49 +02:00
|
|
|
func DetermineLocale() string {
|
|
|
|
truncated := strings.Split(DetermineLang(), ".")[0]
|
|
|
|
if truncated == "" {
|
|
|
|
return "C"
|
|
|
|
}
|
|
|
|
return strings.Replace(truncated, "_", "-", -1)
|
|
|
|
}
|
|
|
|
|
2024-08-09 03:24:54 +02:00
|
|
|
func ClientArch() string {
|
|
|
|
return fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
|
|
|
}
|
|
|
|
|
|
|
|
var releaseRegex = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
|
|
|
|
var osReleaseOnce = &sync.Once{}
|
|
|
|
var osRelease string
|
|
|
|
|
|
|
|
func unameKernelRelease() string {
|
|
|
|
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))
|
|
|
|
m := releaseRegex.FindStringSubmatch(releaseStr)
|
|
|
|
if m == nil || len(m) < 2 {
|
|
|
|
log.Printf("invalid uname -r output: [%s]\n", releaseStr)
|
|
|
|
return "-"
|
|
|
|
}
|
|
|
|
return m[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func UnameKernelRelease() string {
|
|
|
|
osReleaseOnce.Do(func() {
|
|
|
|
osRelease = unameKernelRelease()
|
|
|
|
})
|
|
|
|
return osRelease
|
|
|
|
}
|