Compare commits

..

8 Commits

Author SHA1 Message Date
Evan Simkowitz d9f38f3cbc save 2024-03-15 12:07:24 -04:00
Evan Simkowitz 0649dbb15c Merge branch 'main' into evan/flatpak-deb 2024-03-14 17:24:04 -04:00
Evan Simkowitz 1b122f9da5 save 2024-03-12 17:30:16 -07:00
Evan Simkowitz 8fab81d899 save 2024-03-12 16:22:39 -07:00
Evan Simkowitz 5fa4db8c60 save 2024-03-12 16:13:31 -07:00
Evan Simkowitz 1396303dba save 2024-03-12 15:26:12 -07:00
Evan Simkowitz dcdc9f2b80 Merge branch 'evan/publish-workflow' into evan/flatpak-deb 2024-03-12 14:54:08 -07:00
Evan Simkowitz 81d4b9d3af save work 2024-03-12 14:35:25 -07:00
9 changed files with 346 additions and 54 deletions
+30
View File
@@ -0,0 +1,30 @@
name: Publish a new release
on:
release:
types: [published]
env:
NODE_VERSION: "21.5.0"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v4
with:
node-version: ${{env.NODE_VERSION}}
cache: "yarn"
- name: Get the version
id: get_version
run: |
VERSION=$(node -e 'console.log(require("./version.js"))')
echo "WAVETERM_VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Download staged artifacts
run: |
. ./buildres/download-staged-artifacts.sh
env:
AWS_ACCESS_KEY_ID: "${{ secrets.S3_USERID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.S3_SECRETKEY }}"
AWS_DEFAULT_REGION: us-west-2
- name: Generate Flatpak manifest
run: node buildres/flatpak/generate-flatpak-manifest.js
@@ -0,0 +1,116 @@
app-id: com.visualstudio.code
default-branch: stable
runtime: org.freedesktop.Sdk
runtime-version: '23.08'
sdk: org.freedesktop.Sdk
base: org.electronjs.Electron2.BaseApp
base-version: '23.08'
command: code
tags: [proprietary]
separate-locales: false
finish-args:
- --require-version=0.10.3
- --share=network
- --share=ipc
- --socket=x11
- --socket=pulseaudio
- --socket=ssh-auth
- --device=all
- --allow=devel
- --filesystem=host
- --env=NPM_CONFIG_GLOBALCONFIG=/app/etc/npmrc
- --env=LD_LIBRARY_PATH=/app/lib
# required to fix cursor scaling on wayland
- --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons
- --system-talk-name=org.freedesktop.login1
- --talk-name=org.freedesktop.Notifications
- --talk-name=org.freedesktop.secrets
- --talk-name=org.freedesktop.Flatpak
- --talk-name=com.canonical.AppMenu.Registrar
- --talk-name=com.canonical.AppMenu.Registrar.*
add-extensions:
com.visualstudio.code.tool:
directory: tools
subdirectories: true
version: '23.08'
add-ld-path: lib
no-autodownload: true
cleanup:
- /include
- /lib/pkgconfig
- /share/gtk-doc
- '*.la'
modules:
- shared-modules/libusb/libusb.json
- name: vscode
buildsystem: simple
build-commands:
- install -D code.sh /app/bin/code
- install -Dm644 vscode_64.png /app/share/icons/hicolor/64x64/apps/com.visualstudio.code.png
- install -Dm644 vscode_128.png /app/share/icons/hicolor/128x128/apps/com.visualstudio.code.png
- install -Dm644 vscode_256.png /app/share/icons/hicolor/256x256/apps/com.visualstudio.code.png
- install -Dm644 vscode_512.png /app/share/icons/hicolor/512x512/apps/com.visualstudio.code.png
- install -Dm644 com.visualstudio.code.metainfo.xml -t /app/share/metainfo
- install -Dm644 com.visualstudio.code.desktop -t /app/share/applications
- install -Dm644 com.visualstudio.code-url-handler.desktop -t /app/share/applications
- install -Dm644 com.visualstudio.code-workspace.xml -t /app/share/mime/packages
- install -Dm644 npmrc -t /app/etc
- install -Dm644 flatpak-warning.txt -t /app/share/vscode
- install -D apply_extra -t /app/bin
- cp /usr/bin/ar /app/bin
- ARCH_TRIPLE=$(gcc --print-multiarch) && cp /usr/lib/${ARCH_TRIPLE}/libbfd-*.so
/app/lib
- ARCH_TRIPLE=$(gcc --print-multiarch) && ln -s /usr/lib/${ARCH_TRIPLE}/libtinfo.so/app/lib/libtinfo.so.5
- mkdir /app/tools
sources:
- type: script
dest-filename: apply_extra
commands:
- ar x code.deb
- tar xf data.tar.xz
- mv usr/share/code vscode
- rm -r code.deb control.tar.* data.tar.xz debian-binary usr
- type: file
path: code.sh
- type: file
path: flatpak-warning.txt
- type: file
path: npmrc
- type: file
path: com.visualstudio.code.metainfo.xml
- type: file
path: com.visualstudio.code.desktop
- type: file
path: com.visualstudio.code-url-handler.desktop
- type: file
path: com.visualstudio.code-workspace.xml
- type: file
path: icons/vscode_64.png
- type: file
path: icons/vscode_128.png
- type: file
path: icons/vscode_256.png
- type: file
path: icons/vscode_512.png
- type: extra-data
filename: code.deb
only-arches: [x86_64]
url: <to-replace>
sha256: <to-replace>
size: <to-replace>
x-checker-data:
type: electron-updater
url: https://dl.waveterm.dev/releases/latest-linux.yml
is-main-source: true
- name: host-spawn
buildsystem: simple
build-commands:
- install -Dm755 host-spawn /app/bin/host-spawn
sources:
- type: file
url: https://github.com/1player/host-spawn/releases/download/1.5.0/host-spawn-x86_64
sha256: dbf67e7e111c4fe1edb0c642cbb4193064ca5b384aeb1054fc2befba6ed88b83
dest-filename: host-spawn
only-arches: [x86_64]
@@ -0,0 +1,16 @@
// Updates the Flatpak manifest with the latest values for the staged artifact
// Usage: node generate-flatpak-manifest.js <path-to-staging-directory>
const path = require("path");
const fs = require("fs");
const yaml = require("yaml");
async function generateFlatpakManifest(stagingDir) {
const latestYmlPath = path.join(stagingDir, "latest-linux.yml");
const latestYml = yaml.parse(fs.readFileSync(latestYmlPath, "utf8"));
const manifestPath = path.join(stagingDir, "dev.commandline.waveterm.yml");
const manifestYml = yaml.parse(fs.readFileSync(manifestPath, "utf8"));
const latestDeb = latestYml.files.find((file) => file.url.endsWith(".deb"));
const latestDebUrl = `https://dl.waveterm.dev/releases/${latestDeb.url}`;
const latestDebSha512 = latestDeb.sha512;
+5 -14
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { GlobalModel } from "@/models";
import { Choose, When, If } from "tsx-control-statements/components";
import { Modal, PasswordField, TextField, Markdown, Checkbox } from "@/elements";
import { Modal, PasswordField, Markdown, Checkbox } from "@/elements";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import "./userinput.less";
@@ -82,17 +82,8 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
</If>
<If condition={!userInputRequest.markdown}>{userInputRequest.querytext}</If>
</div>
<If condition={userInputRequest.responsetype == "text"}>
<If condition={userInputRequest.publictext}>
<TextField
onChange={setResponseText}
value={responseText}
maxLength={400}
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</If>
<If condition={!userInputRequest.publictext}>
<Choose>
<When condition={userInputRequest.responsetype == "text"}>
<PasswordField
onChange={setResponseText}
value={responseText}
@@ -100,8 +91,8 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
</If>
</If>
</When>
</Choose>
</div>
<If condition={userInputRequest.checkboxmsg != ""}>
<Checkbox
-1
View File
@@ -667,7 +667,6 @@ declare global {
markdown: boolean;
timeoutms: number;
checkboxmsg: string;
publictext: boolean;
};
type UserInputResponsePacket = {
+12 -17
View File
@@ -126,7 +126,7 @@ var SetVarScopes = []SetVarScope{
{ScopeName: "remote", VarNames: []string{}},
}
var userHostRe = regexp.MustCompile(`^(sudo@)?([a-zA-Z0-9][a-zA-Z0-9._@\\-]*@)?([a-z0-9][a-z0-9.-]*)(?::([0-9]+))?$`)
var userHostRe = regexp.MustCompile(`^(sudo@)?([a-zA-Z0-9][a-zA-Z0-9._@\\-]*)@([a-z0-9][a-z0-9.-]*)(?::([0-9]+))?$`)
var remoteAliasRe = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9._-]*$")
var genericNameRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_ .()<>,/\"'\\[\\]{}=+$@!*-]*$")
var rendererRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_.:-]*$")
@@ -1796,7 +1796,6 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b
return nil, fmt.Errorf("invalid format of user@host argument")
}
sudoStr, remoteUser, remoteHost, remotePortStr := m[1], m[2], m[3], m[4]
remoteUser = strings.Trim(remoteUser, "@")
var uhPort int
if remotePortStr != "" {
var err error
@@ -1838,11 +1837,7 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b
return nil, fmt.Errorf("invalid port argument, \"%d\" is not in the range of 1 to 65535", portVal)
}
sshOpts.SSHPort = portVal
if remoteUser == "" {
canonicalName = remoteHost
} else {
canonicalName = remoteUser + "@" + remoteHost
}
canonicalName = remoteUser + "@" + remoteHost
if portVal != 0 && portVal != 22 {
canonicalName = canonicalName + ":" + strconv.Itoa(portVal)
}
@@ -2141,17 +2136,17 @@ func createSshImportSummary(changeList map[string][]string) string {
func NewHostInfo(hostName string) (*HostInfoType, error) {
userName, _ := ssh_config.GetStrict(hostName, "User")
var canonicalName string
if userName != "" {
canonicalName = userName + "@" + hostName
} else {
canonicalName = hostName
if userName == "" {
// we cannot store a remote with a missing user
// in the current setup
return nil, fmt.Errorf("could not parse \"%s\" - no User in config\n", hostName)
}
canonicalName := userName + "@" + hostName
// check if canonicalname is okay
// check if user and host are okay
m := userHostRe.FindStringSubmatch(canonicalName)
if m == nil {
return nil, fmt.Errorf("could not parse \"%s\" - %s did not fit user@host requirement", hostName, canonicalName)
if m == nil || m[2] == "" || m[3] == "" {
return nil, fmt.Errorf("could not parse \"%s\" - %s did not fit user@host requirement\n", hostName, canonicalName)
}
portStr, _ := ssh_config.GetStrict(hostName, "Port")
@@ -2162,10 +2157,10 @@ func NewHostInfo(hostName string) (*HostInfoType, error) {
portVal, err = strconv.Atoi(portStr)
if err != nil {
// do not make assumptions about port if incorrectly configured
return nil, fmt.Errorf("could not parse \"%s\" (%s) - %s could not be converted to a valid port", hostName, canonicalName, portStr)
return nil, fmt.Errorf("could not parse \"%s\" (%s) - %s could not be converted to a valid port\n", hostName, canonicalName, portStr)
}
if portVal <= 0 || portVal > 65535 {
return nil, fmt.Errorf("could not parse port \"%d\": number is not valid for a port", portVal)
return nil, fmt.Errorf("could not parse port \"%d\": number is not valid for a port\n", portVal)
}
}
identityFile, _ := ssh_config.GetStrict(hostName, "IdentityFile")
+167 -20
View File
@@ -15,7 +15,6 @@ import (
"os/exec"
"path"
"regexp"
"runtime/debug"
"strconv"
"strings"
"sync"
@@ -45,6 +44,8 @@ import (
"golang.org/x/mod/semver"
)
const UseSshLibrary = true
const RemoteTypeMShell = "mshell"
const DefaultTerm = "xterm-256color"
const DefaultMaxPtySize = 1024 * 1024
@@ -128,6 +129,12 @@ type pendingStateKey struct {
RemotePtr sstore.RemotePtrType
}
// for conditional launch method based on ssh library in use
// remove once ssh library is stabilized
type Launcher interface {
Launch(*MShellProc, bool)
}
type MShellProc struct {
Lock *sync.Mutex
Remote *sstore.RemoteType
@@ -156,6 +163,7 @@ type MShellProc struct {
RunningCmds map[base.CommandKey]*RunCmdType
PendingStateCmds map[pendingStateKey]base.CommandKey // key=[remoteinstance name]
launcher Launcher // for conditional launch method based on ssh library in use. remove once ssh library is stabilized
Client *ssh.Client
}
@@ -180,6 +188,12 @@ func CanComplete(remoteType string) bool {
}
}
// for conditional launch method based on ssh library in use
// remove once ssh library is stabilized
func (msh *MShellProc) Launch(interactive bool) {
msh.launcher.Launch(msh, interactive)
}
func (msh *MShellProc) GetStatus() string {
msh.Lock.Lock()
defer msh.Lock.Unlock()
@@ -697,8 +711,14 @@ func MakeMShell(r *sstore.RemoteType) *MShellProc {
RunningCmds: make(map[base.CommandKey]*RunCmdType),
PendingStateCmds: make(map[pendingStateKey]base.CommandKey),
StateMap: server.MakeShellStateMap(),
launcher: LegacyLauncher{}, // for conditional launch method based on ssh library in use. remove once ssh library is stabilized
DataPosMap: utilfn.MakeSyncMap[base.CommandKey, int64](),
}
// for conditional launch method based on ssh library in use
// remove once ssh library is stabilized
if UseSshLibrary {
rtn.launcher = NewLauncher{}
}
rtn.WriteToPtyBuffer("console for connection [%s]\n", r.GetName())
return rtn
@@ -1181,15 +1201,6 @@ func (msh *MShellProc) WaitAndSendPassword(pw string) {
}
func (msh *MShellProc) RunInstall(autoInstall bool) {
defer func() {
if r := recover(); r != nil {
errMsg := fmt.Errorf("this should not happen. if it does, please reach out to us in our discord or open an issue on our github\n\n"+
"error:\n%v\n\nstack trace:\n%s", r, string(debug.Stack()))
log.Printf("fatal error, %s\n", errMsg)
msh.WriteToPtyBuffer("*fatal error, %s\n", errMsg)
msh.setErrorStatus(errMsg)
}
}()
remoteCopy := msh.GetRemoteCopy()
if remoteCopy.Archived {
msh.WriteToPtyBuffer("*error: cannot install on archived remote\n")
@@ -1563,16 +1574,12 @@ func (msh *MShellProc) createWaveshellSession(clientCtx context.Context, remoteC
return wsSession, nil
}
func (msh *MShellProc) Launch(interactive bool) {
defer func() {
if r := recover(); r != nil {
errMsg := fmt.Errorf("this should not happen. if it does, please reach out to us in our discord or open an issue on our github\n\n"+
"error:\n%v\n\nstack trace:\n%s", r, string(debug.Stack()))
log.Printf("fatal error, %s\n", errMsg)
msh.WriteToPtyBuffer("*fatal error, %s\n", errMsg)
msh.setErrorStatus(errMsg)
}
}()
// for conditional launch method based on ssh library in use
// remove once ssh library is stabilized
type NewLauncher struct{}
// func (msh *MShellProc) LaunchNew(interactive bool) {
func (NewLauncher) Launch(msh *MShellProc, interactive bool) {
remoteCopy := msh.GetRemoteCopy()
if remoteCopy.Archived {
msh.WriteToPtyBuffer("cannot launch archived remote\n")
@@ -1680,6 +1687,146 @@ func (msh *MShellProc) Launch(interactive bool) {
go msh.NotifyRemoteUpdate()
}
// for conditional launch method based on ssh library in use
// remove once ssh library is stabilized
type LegacyLauncher struct{}
// func (msh *MShellProc) LaunchLegacy(interactive bool) {
func (LegacyLauncher) Launch(msh *MShellProc, interactive bool) {
remoteCopy := msh.GetRemoteCopy()
if remoteCopy.Archived {
msh.WriteToPtyBuffer("cannot launch archived remote\n")
return
}
curStatus := msh.GetStatus()
if curStatus == StatusConnected {
msh.WriteToPtyBuffer("remote is already connected (no action taken)\n")
return
}
if curStatus == StatusConnecting {
msh.WriteToPtyBuffer("remote is already connecting, disconnect before trying to connect again\n")
return
}
sapi, err := shellapi.MakeShellApi(msh.GetShellType())
if err != nil {
msh.WriteToPtyBuffer("*error, %v\n", err)
return
}
istatus := msh.GetInstallStatus()
if istatus == StatusConnecting {
msh.WriteToPtyBuffer("remote is trying to install, cancel install before trying to connect again\n")
return
}
if remoteCopy.SSHOpts.SSHPort != 0 && remoteCopy.SSHOpts.SSHPort != 22 {
msh.WriteToPtyBuffer("connecting to %s (port %d)...\n", remoteCopy.RemoteCanonicalName, remoteCopy.SSHOpts.SSHPort)
} else {
msh.WriteToPtyBuffer("connecting to %s...\n", remoteCopy.RemoteCanonicalName)
}
sshOpts := convertSSHOpts(remoteCopy.SSHOpts)
sshOpts.SSHErrorsToTty = true
if remoteCopy.ConnectMode != sstore.ConnectModeManual && remoteCopy.SSHOpts.SSHPassword == "" && !interactive {
sshOpts.BatchMode = true
}
var cmdStr string
if sshOpts.SSHHost == "" && remoteCopy.Local {
var err error
cmdStr, err = MakeLocalMShellCommandStr(remoteCopy.IsSudo())
if err != nil {
msh.WriteToPtyBuffer("*error, cannot find local mshell binary: %v\n", err)
return
}
} else {
cmdStr = MakeServerCommandStr()
}
ecmd := sshOpts.MakeSSHExecCmd(cmdStr, sapi)
cmdPty, err := msh.addControllingTty(ecmd)
if err != nil {
statusErr := fmt.Errorf("cannot attach controlling tty to mshell command: %w", err)
msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error())
msh.setErrorStatus(statusErr)
return
}
defer func() {
if len(ecmd.ExtraFiles) > 0 {
ecmd.ExtraFiles[len(ecmd.ExtraFiles)-1].Close()
}
}()
go msh.RunPtyReadLoop(cmdPty)
if remoteCopy.SSHOpts.SSHPassword != "" {
go msh.WaitAndSendPassword(remoteCopy.SSHOpts.SSHPassword)
}
var makeClientCtx context.Context
var makeClientCancelFn context.CancelFunc
msh.WithLock(func() {
deadlineTime := time.Now().Add(RemoteConnectTimeout)
makeClientCtx, makeClientCancelFn = context.WithDeadline(context.Background(), deadlineTime)
defer makeClientCancelFn()
msh.Err = nil
msh.ErrNoInitPk = false
msh.Status = StatusConnecting
msh.MakeClientCancelFn = makeClientCancelFn
msh.MakeClientDeadline = &deadlineTime
go msh.NotifyRemoteUpdate()
})
go msh.watchClientDeadlineTime()
cproc, err := shexec.MakeClientProc(makeClientCtx, shexec.CmdWrap{Cmd: ecmd})
msh.WithLock(func() {
msh.MakeClientCancelFn = nil
msh.MakeClientDeadline = nil
msh.StateMap.Clear()
// no notify here, because we'll call notify in either case below
})
if err == context.DeadlineExceeded {
msh.WriteToPtyBuffer("*connect timeout\n")
msh.setErrorStatus(errors.New("connect timeout"))
return
} else if err == context.Canceled {
msh.WriteToPtyBuffer("*forced disconnection\n")
msh.WithLock(func() {
msh.Status = StatusDisconnected
go msh.NotifyRemoteUpdate()
})
return
} else if serr, ok := err.(shexec.WaveshellLaunchError); ok {
msh.WithLock(func() {
msh.UName = serr.InitPk.UName
if semver.Compare(serr.InitPk.Version, scbase.MShellVersion) < 0 {
// only set NeedsMShellUpgrade if we got an InitPk
msh.NeedsMShellUpgrade = true
}
msh.InitPkShellType = serr.InitPk.Shell
})
msh.WriteToPtyBuffer("*error, %s\n", serr.Error())
msh.setErrorStatus(serr)
go msh.tryAutoInstall()
return
} else if err != nil {
msh.WriteToPtyBuffer("*error, %s\n", serr.Error())
msh.setErrorStatus(err)
return
}
msh.updateRemoteStateVars(context.Background(), msh.RemoteId, cproc.InitPk)
msh.WithLock(func() {
msh.ServerProc = cproc
msh.Status = StatusConnected
})
go func() {
exitErr := cproc.Cmd.Wait()
exitCode := shexec.GetExitCode(exitErr)
msh.WithLock(func() {
if msh.Status == StatusConnected || msh.Status == StatusConnecting {
msh.Status = StatusDisconnected
go msh.NotifyRemoteUpdate()
}
})
msh.WriteToPtyBuffer("*disconnected exitcode=%d\n", exitCode)
}()
go msh.ProcessPackets()
msh.initActiveShells()
go msh.NotifyRemoteUpdate()
}
func (msh *MShellProc) initActiveShells() {
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn()
-1
View File
@@ -230,7 +230,6 @@ func promptChallengeQuestion(connCtx context.Context, question string, echo bool
QueryText: queryText,
Markdown: true,
Title: "Keyboard Interactive Authentication",
PublicText: echo,
}
response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request)
if err != nil {
-1
View File
@@ -22,7 +22,6 @@ type UserInputRequestType struct {
Markdown bool `json:"markdown"`
TimeoutMs int `json:"timeoutms"`
CheckBoxMsg string `json:"checkboxmsg"`
PublicText bool `json:"publictext"`
}
func (*UserInputRequestType) GetType() string {