mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-24 17:08:26 +01:00
SSH without using WSH (#1355)
This commit is contained in:
parent
c37d292224
commit
24103213aa
@ -27,10 +27,8 @@ func GenerateWshClient() error {
|
||||
"github.com/wavetermdev/waveterm/pkg/wshutil",
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc",
|
||||
"github.com/wavetermdev/waveterm/pkg/waveobj",
|
||||
"github.com/wavetermdev/waveterm/pkg/wconfig",
|
||||
"github.com/wavetermdev/waveterm/pkg/wps",
|
||||
"github.com/wavetermdev/waveterm/pkg/vdom",
|
||||
"github.com/wavetermdev/waveterm/pkg/telemetry",
|
||||
})
|
||||
wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
|
||||
for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {
|
||||
|
@ -113,7 +113,7 @@ func telemetryLoop() {
|
||||
}
|
||||
|
||||
func panicTelemetryHandler() {
|
||||
activity := telemetry.ActivityUpdate{NumPanics: 1}
|
||||
activity := wshrpc.ActivityUpdate{NumPanics: 1}
|
||||
err := telemetry.UpdateActivity(context.Background(), activity)
|
||||
if err != nil {
|
||||
log.Printf("error updating activity (panicTelemetryHandler): %v\n", err)
|
||||
@ -137,7 +137,7 @@ func sendTelemetryWrapper() {
|
||||
}
|
||||
|
||||
func beforeSendActivityUpdate(ctx context.Context) {
|
||||
activity := telemetry.ActivityUpdate{}
|
||||
activity := wshrpc.ActivityUpdate{}
|
||||
activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx)
|
||||
activity.NumBlocks, _ = wstore.DBGetCount[*waveobj.Block](ctx)
|
||||
activity.Blocks, _ = wstore.DBGetBlockViewCounts(ctx)
|
||||
@ -153,7 +153,7 @@ func beforeSendActivityUpdate(ctx context.Context) {
|
||||
func startupActivityUpdate() {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancelFn()
|
||||
activity := telemetry.ActivityUpdate{Startup: 1}
|
||||
activity := wshrpc.ActivityUpdate{Startup: 1}
|
||||
err := telemetry.UpdateActivity(ctx, activity) // set at least one record into activity (don't use go routine wrap here)
|
||||
if err != nil {
|
||||
log.Printf("error updating startup activity: %v\n", err)
|
||||
@ -163,7 +163,7 @@ func startupActivityUpdate() {
|
||||
func shutdownActivityUpdate() {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancelFn()
|
||||
activity := telemetry.ActivityUpdate{Shutdown: 1}
|
||||
activity := wshrpc.ActivityUpdate{Shutdown: 1}
|
||||
err := telemetry.UpdateActivity(ctx, activity) // do NOT use the go routine wrap here (this needs to be synchronous)
|
||||
if err != nil {
|
||||
log.Printf("error updating shutdown activity: %v\n", err)
|
||||
|
@ -167,7 +167,7 @@ func connConnectRun(cmd *cobra.Command, args []string) error {
|
||||
if err := validateConnectionName(connName); err != nil {
|
||||
return err
|
||||
}
|
||||
err := wshclient.ConnConnectCommand(RpcClient, connName, &wshrpc.RpcOpts{Timeout: 60000})
|
||||
err := wshclient.ConnConnectCommand(RpcClient, wshrpc.ConnRequest{Host: connName}, &wshrpc.RpcOpts{Timeout: 60000})
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting connection: %w", err)
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||||
)
|
||||
@ -34,7 +33,7 @@ func setConfigRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commandData := wconfig.MetaSettingsType{MetaMapType: meta}
|
||||
commandData := wshrpc.MetaSettingsType{MetaMapType: meta}
|
||||
err = wshclient.SetConfigCommand(RpcClient, commandData, &wshrpc.RpcOpts{Timeout: 2000})
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting config: %w", err)
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||||
)
|
||||
|
||||
var identityFiles []string
|
||||
|
||||
var sshCmd = &cobra.Command{
|
||||
Use: "ssh",
|
||||
Short: "connect this terminal to a remote host",
|
||||
@ -21,6 +23,7 @@ var sshCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
sshCmd.Flags().StringArrayVarP(&identityFiles, "identityfile", "i", []string{}, "add an identity file for publickey authentication")
|
||||
rootCmd.AddCommand(sshCmd)
|
||||
}
|
||||
|
||||
@ -34,6 +37,16 @@ func sshRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
||||
if blockId == "" {
|
||||
return fmt.Errorf("cannot determine blockid (not in JWT)")
|
||||
}
|
||||
// first, make a connection independent of the block
|
||||
connOpts := wshrpc.ConnRequest{
|
||||
Host: sshArg,
|
||||
Keywords: wshrpc.ConnKeywords{
|
||||
SshIdentityFile: identityFiles,
|
||||
},
|
||||
}
|
||||
wshclient.ConnConnectCommand(RpcClient, connOpts, nil)
|
||||
|
||||
// now, with that made, it will be straightforward to connect
|
||||
data := wshrpc.CommandSetMetaData{
|
||||
ORef: waveobj.MakeORef(waveobj.OType_Block, blockId),
|
||||
Meta: map[string]any{
|
||||
|
@ -341,7 +341,7 @@ const ConnStatusOverlay = React.memo(
|
||||
}, [width, connStatus, setShowError]);
|
||||
|
||||
const handleTryReconnect = React.useCallback(() => {
|
||||
const prtn = RpcApi.ConnConnectCommand(TabRpcClient, connName, { timeout: 60000 });
|
||||
const prtn = RpcApi.ConnConnectCommand(TabRpcClient, { host: connName }, { timeout: 60000 });
|
||||
prtn.catch((e) => console.log("error reconnecting", connName, e));
|
||||
}, [connName]);
|
||||
|
||||
@ -673,7 +673,11 @@ const ChangeConnectionBlockModal = React.memo(
|
||||
label: `Reconnect to ${connStatus.connection}`,
|
||||
value: "",
|
||||
onSelect: async (_: string) => {
|
||||
const prtn = RpcApi.ConnConnectCommand(TabRpcClient, connStatus.connection, { timeout: 60000 });
|
||||
const prtn = RpcApi.ConnConnectCommand(
|
||||
TabRpcClient,
|
||||
{ host: connStatus.connection },
|
||||
{ timeout: 60000 }
|
||||
);
|
||||
prtn.catch((e) => console.log("error reconnecting", connStatus.connection, e));
|
||||
},
|
||||
};
|
||||
|
@ -45,11 +45,17 @@
|
||||
|
||||
.userinput-checkbox-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
.userinput-checkbox {
|
||||
accent-color: var(--accent-color);
|
||||
.userinput-checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.userinput-checkbox {
|
||||
accent-color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
const [countdown, setCountdown] = useState(Math.floor(userInputRequest.timeoutms / 1000));
|
||||
const checkboxRef = useRef<HTMLInputElement>();
|
||||
|
||||
const handleSendCancel = useCallback(() => {
|
||||
const handleSendErrResponse = useCallback(() => {
|
||||
UserInputService.SendUserInputResponse({
|
||||
type: "userinputresp",
|
||||
requestid: userInputRequest.requestid,
|
||||
@ -29,20 +29,24 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
type: "userinputresp",
|
||||
requestid: userInputRequest.requestid,
|
||||
text: responseText,
|
||||
checkboxstat: checkboxRef.current?.checked ?? false,
|
||||
checkboxstat: checkboxRef?.current?.checked ?? false,
|
||||
});
|
||||
modalsModel.popModal();
|
||||
}, [responseText, userInputRequest]);
|
||||
console.log("bar");
|
||||
|
||||
const handleSendConfirm = useCallback(() => {
|
||||
UserInputService.SendUserInputResponse({
|
||||
type: "userinputresp",
|
||||
requestid: userInputRequest.requestid,
|
||||
confirm: true,
|
||||
checkboxstat: checkboxRef.current?.checked ?? false,
|
||||
});
|
||||
modalsModel.popModal();
|
||||
}, [userInputRequest]);
|
||||
const handleSendConfirm = useCallback(
|
||||
(response: boolean) => {
|
||||
UserInputService.SendUserInputResponse({
|
||||
type: "userinputresp",
|
||||
requestid: userInputRequest.requestid,
|
||||
confirm: response,
|
||||
checkboxstat: checkboxRef?.current?.checked ?? false,
|
||||
});
|
||||
modalsModel.popModal();
|
||||
},
|
||||
[userInputRequest]
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
switch (userInputRequest.responsetype) {
|
||||
@ -50,15 +54,16 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
handleSendText();
|
||||
break;
|
||||
case "confirm":
|
||||
handleSendConfirm();
|
||||
handleSendConfirm(true);
|
||||
break;
|
||||
}
|
||||
}, [handleSendConfirm, handleSendText, userInputRequest.responsetype]);
|
||||
console.log("baz");
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(waveEvent: WaveKeyboardEvent): boolean => {
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
|
||||
handleSendCancel();
|
||||
handleSendErrResponse();
|
||||
return;
|
||||
}
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
||||
@ -66,7 +71,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
[handleSendCancel, handleSubmit]
|
||||
[handleSendErrResponse, handleSubmit]
|
||||
);
|
||||
|
||||
const queryText = useMemo(() => {
|
||||
@ -75,6 +80,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
}
|
||||
return <span className="userinput-text">{userInputRequest.querytext}</span>;
|
||||
}, [userInputRequest.markdown, userInputRequest.querytext]);
|
||||
console.log("foobarbaz");
|
||||
|
||||
const inputBox = useMemo(() => {
|
||||
if (userInputRequest.responsetype === "confirm") {
|
||||
@ -92,6 +98,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
/>
|
||||
);
|
||||
}, [userInputRequest.responsetype, userInputRequest.publictext, responseText, handleKeyDown, setResponseText]);
|
||||
console.log("mem1");
|
||||
|
||||
const optionalCheckbox = useMemo(() => {
|
||||
if (userInputRequest.checkboxmsg == "") {
|
||||
@ -99,22 +106,25 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
}
|
||||
return (
|
||||
<div className="userinput-checkbox-container">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`uicheckbox-${userInputRequest.requestid}`}
|
||||
className="userinput-checkbox"
|
||||
ref={checkboxRef}
|
||||
/>
|
||||
<label htmlFor={`uicheckbox-${userInputRequest.requestid}}`}>{userInputRequest.checkboxmsg}</label>
|
||||
<div className="userinput-checkbox-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`uicheckbox-${userInputRequest.requestid}`}
|
||||
className="userinput-checkbox"
|
||||
ref={checkboxRef}
|
||||
/>
|
||||
<label htmlFor={`uicheckbox-${userInputRequest.requestid}}`}>{userInputRequest.checkboxmsg}</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
console.log("mem2");
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
if (countdown <= 0) {
|
||||
timeout = setTimeout(() => {
|
||||
handleSendCancel();
|
||||
handleSendErrResponse();
|
||||
}, 300);
|
||||
} else {
|
||||
timeout = setTimeout(() => {
|
||||
@ -123,9 +133,28 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
}
|
||||
return () => clearTimeout(timeout);
|
||||
}, [countdown]);
|
||||
console.log("count");
|
||||
|
||||
const handleNegativeResponse = useCallback(() => {
|
||||
switch (userInputRequest.responsetype) {
|
||||
case "text":
|
||||
handleSendErrResponse();
|
||||
break;
|
||||
case "confirm":
|
||||
handleSendConfirm(false);
|
||||
break;
|
||||
}
|
||||
}, [userInputRequest.responsetype, handleSendErrResponse, handleSendConfirm]);
|
||||
console.log("before end");
|
||||
|
||||
return (
|
||||
<Modal onOk={() => handleSubmit()} onCancel={() => handleSendCancel()} onClose={() => handleSendCancel()}>
|
||||
<Modal
|
||||
onOk={() => handleSubmit()}
|
||||
onCancel={() => handleNegativeResponse()}
|
||||
onClose={() => handleSendErrResponse()}
|
||||
okLabel={userInputRequest.oklabel}
|
||||
cancelLabel={userInputRequest.cancellabel}
|
||||
>
|
||||
<div className="userinput-header">{userInputRequest.title + ` (${countdown}s)`}</div>
|
||||
<div className="userinput-body">
|
||||
{queryText}
|
||||
|
@ -28,7 +28,7 @@ class RpcApiType {
|
||||
}
|
||||
|
||||
// command "connconnect" [call]
|
||||
ConnConnectCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<void> {
|
||||
ConnConnectCommand(client: WshClient, data: ConnRequest, opts?: RpcOpts): Promise<void> {
|
||||
return client.wshRpcCall("connconnect", data, opts);
|
||||
}
|
||||
|
||||
|
36
frontend/types/gotypes.d.ts
vendored
36
frontend/types/gotypes.d.ts
vendored
@ -5,7 +5,7 @@
|
||||
|
||||
declare global {
|
||||
|
||||
// telemetry.ActivityDisplayType
|
||||
// wshrpc.ActivityDisplayType
|
||||
type ActivityDisplayType = {
|
||||
width: number;
|
||||
height: number;
|
||||
@ -13,7 +13,7 @@ declare global {
|
||||
internal?: boolean;
|
||||
};
|
||||
|
||||
// telemetry.ActivityUpdate
|
||||
// wshrpc.ActivityUpdate
|
||||
type ActivityUpdate = {
|
||||
fgminutes?: number;
|
||||
activeminutes?: number;
|
||||
@ -275,9 +275,36 @@ declare global {
|
||||
err: string;
|
||||
};
|
||||
|
||||
// wshrpc.ConnKeywords
|
||||
type ConnKeywords = {
|
||||
wshenabled?: boolean;
|
||||
askbeforewshinstall?: boolean;
|
||||
"ssh:user"?: string;
|
||||
"ssh:hostname"?: string;
|
||||
"ssh:port"?: string;
|
||||
"ssh:identityfile"?: string[];
|
||||
"ssh:batchmode"?: boolean;
|
||||
"ssh:pubkeyauthentication"?: boolean;
|
||||
"ssh:passwordauthentication"?: boolean;
|
||||
"ssh:kbdinteractiveauthentication"?: boolean;
|
||||
"ssh:preferredauthentications"?: string[];
|
||||
"ssh:addkeystoagent"?: boolean;
|
||||
"ssh:identityagent"?: string;
|
||||
"ssh:proxyjump"?: string[];
|
||||
"ssh:userknownhostsfile"?: string[];
|
||||
"ssh:globalknownhostsfile"?: string[];
|
||||
};
|
||||
|
||||
// wshrpc.ConnRequest
|
||||
type ConnRequest = {
|
||||
host: string;
|
||||
keywords?: ConnKeywords;
|
||||
};
|
||||
|
||||
// wshrpc.ConnStatus
|
||||
type ConnStatus = {
|
||||
status: string;
|
||||
wshenabled: boolean;
|
||||
connection: string;
|
||||
connected: boolean;
|
||||
hasconnected: boolean;
|
||||
@ -337,9 +364,11 @@ declare global {
|
||||
type FullConfigType = {
|
||||
settings: SettingsType;
|
||||
mimetypes: {[key: string]: MimeTypeConfigType};
|
||||
defaultwidgets: {[key: string]: WidgetConfigType};
|
||||
widgets: {[key: string]: WidgetConfigType};
|
||||
presets: {[key: string]: MetaType};
|
||||
termthemes: {[key: string]: TermThemeType};
|
||||
connections: {[key: string]: ConnKeywords};
|
||||
configerrors: ConfigError[];
|
||||
};
|
||||
|
||||
@ -608,6 +637,7 @@ declare global {
|
||||
"telemetry:enabled"?: boolean;
|
||||
"conn:*"?: boolean;
|
||||
"conn:askbeforewshinstall"?: boolean;
|
||||
"conn:wshenabled"?: boolean;
|
||||
};
|
||||
|
||||
// waveobj.StickerClickOptsType
|
||||
@ -701,6 +731,8 @@ declare global {
|
||||
timeoutms: number;
|
||||
checkboxmsg: string;
|
||||
publictext: boolean;
|
||||
oklabel?: string;
|
||||
cancellabel?: string;
|
||||
};
|
||||
|
||||
// userinput.UserInputResponse
|
||||
|
@ -296,7 +296,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := conncontroller.GetConn(credentialCtx, opts, false)
|
||||
conn := conncontroller.GetConn(credentialCtx, opts, false, &wshrpc.ConnKeywords{})
|
||||
connStatus := conn.DeriveConnStatus()
|
||||
if connStatus.Status != conncontroller.Status_Connected {
|
||||
return fmt.Errorf("not connected, cannot start shellproc")
|
||||
@ -538,7 +538,7 @@ func CheckConnStatus(blockId string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing connection name: %w", err)
|
||||
}
|
||||
conn := conncontroller.GetConn(context.Background(), opts, false)
|
||||
conn := conncontroller.GetConn(context.Background(), opts, false, &wshrpc.ConnKeywords{})
|
||||
connStatus := conn.DeriveConnStatus()
|
||||
if connStatus.Status != conncontroller.Status_Connected {
|
||||
return fmt.Errorf("not connected: %s", connStatus.Status)
|
||||
|
@ -52,6 +52,7 @@ var activeConnCounter = &atomic.Int32{}
|
||||
type SSHConn struct {
|
||||
Lock *sync.Mutex
|
||||
Status string
|
||||
WshEnabled *atomic.Bool
|
||||
Opts *remote.SSHOpts
|
||||
Client *ssh.Client
|
||||
SockName string
|
||||
@ -290,6 +291,12 @@ type WshInstallOpts struct {
|
||||
NoUserPrompt bool
|
||||
}
|
||||
|
||||
type WshInstallSkipError struct{}
|
||||
|
||||
func (wise *WshInstallSkipError) Error() string {
|
||||
return "skipping wsh installation"
|
||||
}
|
||||
|
||||
func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName string, opts *WshInstallOpts) error {
|
||||
if opts == nil {
|
||||
opts = &WshInstallOpts{}
|
||||
@ -325,12 +332,23 @@ func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName s
|
||||
QueryText: queryText,
|
||||
Title: title,
|
||||
Markdown: true,
|
||||
CheckBoxMsg: "Don't show me this again",
|
||||
CheckBoxMsg: "Automatically install for all connections",
|
||||
OkLabel: "Install wsh",
|
||||
CancelLabel: "No wsh",
|
||||
}
|
||||
response, err := userinput.GetUserInput(ctx, request)
|
||||
if err != nil || !response.Confirm {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Confirm {
|
||||
meta := make(map[string]any)
|
||||
meta["wshenabled"] = false
|
||||
err = wconfig.SetConnectionsConfigValue(conn.GetName(), meta)
|
||||
if err != nil {
|
||||
log.Printf("warning: error writing to connections file: %v", err)
|
||||
}
|
||||
return &WshInstallSkipError{}
|
||||
}
|
||||
if response.CheckboxStat {
|
||||
meta := waveobj.MetaMapType{
|
||||
wconfig.ConfigKey_ConnAskBeforeWshInstall: false,
|
||||
@ -371,7 +389,7 @@ func (conn *SSHConn) Reconnect(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return conn.Connect(ctx)
|
||||
return conn.Connect(ctx, &wshrpc.ConnKeywords{})
|
||||
}
|
||||
|
||||
func (conn *SSHConn) WaitForConnect(ctx context.Context) error {
|
||||
@ -399,7 +417,7 @@ func (conn *SSHConn) WaitForConnect(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// does not return an error since that error is stored inside of SSHConn
|
||||
func (conn *SSHConn) Connect(ctx context.Context) error {
|
||||
func (conn *SSHConn) Connect(ctx context.Context, connFlags *wshrpc.ConnKeywords) error {
|
||||
var connectAllowed bool
|
||||
conn.WithLock(func() {
|
||||
if conn.Status == Status_Connecting || conn.Status == Status_Connected {
|
||||
@ -415,13 +433,13 @@ func (conn *SSHConn) Connect(ctx context.Context) error {
|
||||
return fmt.Errorf("cannot connect to %q when status is %q", conn.GetName(), conn.GetStatus())
|
||||
}
|
||||
conn.FireConnChangeEvent()
|
||||
err := conn.connectInternal(ctx)
|
||||
err := conn.connectInternal(ctx, connFlags)
|
||||
conn.WithLock(func() {
|
||||
if err != nil {
|
||||
conn.Status = Status_Error
|
||||
conn.Error = err.Error()
|
||||
conn.close_nolock()
|
||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
||||
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||
Conn: map[string]int{"ssh:connecterror": 1},
|
||||
}, "ssh-connconnect")
|
||||
} else {
|
||||
@ -430,7 +448,7 @@ func (conn *SSHConn) Connect(ctx context.Context) error {
|
||||
if conn.ActiveConnNum == 0 {
|
||||
conn.ActiveConnNum = int(activeConnCounter.Add(1))
|
||||
}
|
||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
||||
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||
Conn: map[string]int{"ssh:connect": 1},
|
||||
}, "ssh-connconnect")
|
||||
}
|
||||
@ -445,8 +463,8 @@ func (conn *SSHConn) WithLock(fn func()) {
|
||||
fn()
|
||||
}
|
||||
|
||||
func (conn *SSHConn) connectInternal(ctx context.Context) error {
|
||||
client, _, err := remote.ConnectToClient(ctx, conn.Opts, nil, 0)
|
||||
func (conn *SSHConn) connectInternal(ctx context.Context, connFlags *wshrpc.ConnKeywords) error {
|
||||
client, _, err := remote.ConnectToClient(ctx, conn.Opts, nil, 0, connFlags)
|
||||
if err != nil {
|
||||
log.Printf("error: failed to connect to client %s: %s\n", conn.GetName(), err)
|
||||
return err
|
||||
@ -462,15 +480,40 @@ func (conn *SSHConn) connectInternal(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
config := wconfig.ReadFullConfig()
|
||||
installErr := conn.CheckAndInstallWsh(ctx, clientDisplayName, &WshInstallOpts{NoUserPrompt: !config.Settings.ConnAskBeforeWshInstall})
|
||||
if installErr != nil {
|
||||
log.Printf("error: unable to install wsh shell extensions for %s: %v\n", conn.GetName(), err)
|
||||
return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr)
|
||||
enableWsh := config.Settings.ConnWshEnabled
|
||||
askBeforeInstall := config.Settings.ConnAskBeforeWshInstall
|
||||
connSettings, ok := config.Connections[conn.GetName()]
|
||||
if ok {
|
||||
if connSettings.WshEnabled != nil {
|
||||
enableWsh = *connSettings.WshEnabled
|
||||
}
|
||||
if connSettings.AskBeforeWshInstall != nil {
|
||||
askBeforeInstall = *connSettings.AskBeforeWshInstall
|
||||
}
|
||||
}
|
||||
csErr := conn.StartConnServer()
|
||||
if csErr != nil {
|
||||
log.Printf("error: unable to start conn server for %s: %v\n", conn.GetName(), csErr)
|
||||
return fmt.Errorf("conncontroller %s start wsh connserver error: %v", conn.GetName(), csErr)
|
||||
if enableWsh {
|
||||
installErr := conn.CheckAndInstallWsh(ctx, clientDisplayName, &WshInstallOpts{NoUserPrompt: !askBeforeInstall})
|
||||
if errors.Is(installErr, &WshInstallSkipError{}) {
|
||||
// skips are not true errors
|
||||
conn.WithLock(func() {
|
||||
conn.WshEnabled.Store(false)
|
||||
})
|
||||
} else if installErr != nil {
|
||||
log.Printf("error: unable to install wsh shell extensions for %s: %v\n", conn.GetName(), err)
|
||||
return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr)
|
||||
} else {
|
||||
conn.WshEnabled.Store(true)
|
||||
}
|
||||
|
||||
if conn.WshEnabled.Load() {
|
||||
csErr := conn.StartConnServer()
|
||||
if csErr != nil {
|
||||
log.Printf("error: unable to start conn server for %s: %v\n", conn.GetName(), csErr)
|
||||
return fmt.Errorf("conncontroller %s start wsh connserver error: %v", conn.GetName(), csErr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
conn.WshEnabled.Store(false)
|
||||
}
|
||||
conn.HasWaiter.Store(true)
|
||||
go conn.waitForDisconnect()
|
||||
@ -504,16 +547,16 @@ func getConnInternal(opts *remote.SSHOpts) *SSHConn {
|
||||
defer globalLock.Unlock()
|
||||
rtn := clientControllerMap[*opts]
|
||||
if rtn == nil {
|
||||
rtn = &SSHConn{Lock: &sync.Mutex{}, Status: Status_Init, Opts: opts, HasWaiter: &atomic.Bool{}}
|
||||
rtn = &SSHConn{Lock: &sync.Mutex{}, Status: Status_Init, WshEnabled: &atomic.Bool{}, Opts: opts, HasWaiter: &atomic.Bool{}}
|
||||
clientControllerMap[*opts] = rtn
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func GetConn(ctx context.Context, opts *remote.SSHOpts, shouldConnect bool) *SSHConn {
|
||||
func GetConn(ctx context.Context, opts *remote.SSHOpts, shouldConnect bool, connFlags *wshrpc.ConnKeywords) *SSHConn {
|
||||
conn := getConnInternal(opts)
|
||||
if conn.Client == nil && shouldConnect {
|
||||
conn.Connect(ctx)
|
||||
conn.Connect(ctx, connFlags)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
@ -527,7 +570,7 @@ func EnsureConnection(ctx context.Context, connName string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing connection name: %w", err)
|
||||
}
|
||||
conn := GetConn(ctx, connOpts, false)
|
||||
conn := GetConn(ctx, connOpts, false, &wshrpc.ConnKeywords{})
|
||||
if conn == nil {
|
||||
return fmt.Errorf("connection not found: %s", connName)
|
||||
}
|
||||
@ -538,7 +581,7 @@ func EnsureConnection(ctx context.Context, connName string) error {
|
||||
case Status_Connecting:
|
||||
return conn.WaitForConnect(ctx)
|
||||
case Status_Init, Status_Disconnected:
|
||||
return conn.Connect(ctx)
|
||||
return conn.Connect(ctx, &wshrpc.ConnKeywords{})
|
||||
case Status_Error:
|
||||
return fmt.Errorf("connection error: %s", connStatus.Error)
|
||||
default:
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -28,6 +27,7 @@ import (
|
||||
"github.com/wavetermdev/waveterm/pkg/userinput"
|
||||
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
||||
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
xknownhosts "golang.org/x/crypto/ssh/knownhosts"
|
||||
@ -101,13 +101,13 @@ func createDummySigner() ([]ssh.Signer, error) {
|
||||
// they were successes. An error in this function prevents any other
|
||||
// keys from being attempted. But if there's an error because of a dummy
|
||||
// file, the library can still try again with a new key.
|
||||
func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, authSockSignersExt []ssh.Signer, agentClient agent.ExtendedAgent, debugInfo *ConnectionDebugInfo) func() ([]ssh.Signer, error) {
|
||||
func createPublicKeyCallback(connCtx context.Context, sshKeywords *wshrpc.ConnKeywords, authSockSignersExt []ssh.Signer, agentClient agent.ExtendedAgent, debugInfo *ConnectionDebugInfo) func() ([]ssh.Signer, error) {
|
||||
var identityFiles []string
|
||||
existingKeys := make(map[string][]byte)
|
||||
|
||||
// checking the file early prevents us from needing to send a
|
||||
// dummy signer if there's a problem with the signer
|
||||
for _, identityFile := range sshKeywords.IdentityFile {
|
||||
for _, identityFile := range sshKeywords.SshIdentityFile {
|
||||
filePath, err := wavebase.ExpandHomeDir(identityFile)
|
||||
if err != nil {
|
||||
continue
|
||||
@ -151,7 +151,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
|
||||
if err == nil {
|
||||
signer, err := ssh.NewSignerFromKey(unencryptedPrivateKey)
|
||||
if err == nil {
|
||||
if sshKeywords.AddKeysToAgent && agentClient != nil {
|
||||
if sshKeywords.SshAddKeysToAgent && agentClient != nil {
|
||||
agentClient.Add(agent.AddedKey{
|
||||
PrivateKey: unencryptedPrivateKey,
|
||||
})
|
||||
@ -165,7 +165,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
|
||||
}
|
||||
|
||||
// batch mode deactivates user input
|
||||
if sshKeywords.BatchMode {
|
||||
if sshKeywords.SshBatchMode {
|
||||
// skip this key and try with the next
|
||||
return createDummySigner()
|
||||
}
|
||||
@ -194,7 +194,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
|
||||
// skip this key and try with the next
|
||||
return createDummySigner()
|
||||
}
|
||||
if sshKeywords.AddKeysToAgent && agentClient != nil {
|
||||
if sshKeywords.SshAddKeysToAgent && agentClient != nil {
|
||||
agentClient.Add(agent.AddedKey{
|
||||
PrivateKey: unencryptedPrivateKey,
|
||||
})
|
||||
@ -333,7 +333,14 @@ func createUnknownKeyVerifier(knownHostsFile string, hostname string, remote str
|
||||
return func() (*userinput.UserInputResponse, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancelFn()
|
||||
return userinput.GetUserInput(ctx, request)
|
||||
resp, err := userinput.GetUserInput(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Confirm {
|
||||
return nil, fmt.Errorf("user selected no")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,7 +364,14 @@ func createMissingKnownHostsVerifier(knownHostsFile string, hostname string, rem
|
||||
return func() (*userinput.UserInputResponse, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancelFn()
|
||||
return userinput.GetUserInput(ctx, request)
|
||||
resp, err := userinput.GetUserInput(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Confirm {
|
||||
return nil, fmt.Errorf("user selected no")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,9 +384,9 @@ func lineContainsMatch(line []byte, matches [][]byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func createHostKeyCallback(sshKeywords *SshKeywords) (ssh.HostKeyCallback, HostKeyAlgorithms, error) {
|
||||
globalKnownHostsFiles := sshKeywords.GlobalKnownHostsFile
|
||||
userKnownHostsFiles := sshKeywords.UserKnownHostsFile
|
||||
func createHostKeyCallback(sshKeywords *wshrpc.ConnKeywords) (ssh.HostKeyCallback, HostKeyAlgorithms, error) {
|
||||
globalKnownHostsFiles := sshKeywords.SshGlobalKnownHostsFile
|
||||
userKnownHostsFiles := sshKeywords.SshUserKnownHostsFile
|
||||
|
||||
osUser, err := user.Current()
|
||||
if err != nil {
|
||||
@ -536,12 +550,12 @@ func createHostKeyCallback(sshKeywords *SshKeywords) (ssh.HostKeyCallback, HostK
|
||||
return waveHostKeyCallback, hostKeyAlgorithms, nil
|
||||
}
|
||||
|
||||
func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debugInfo *ConnectionDebugInfo) (*ssh.ClientConfig, error) {
|
||||
remoteName := sshKeywords.User + "@" + xknownhosts.Normalize(sshKeywords.HostName+":"+sshKeywords.Port)
|
||||
func createClientConfig(connCtx context.Context, sshKeywords *wshrpc.ConnKeywords, debugInfo *ConnectionDebugInfo) (*ssh.ClientConfig, error) {
|
||||
remoteName := sshKeywords.SshUser + "@" + xknownhosts.Normalize(sshKeywords.SshHostName+":"+sshKeywords.SshPort)
|
||||
|
||||
var authSockSigners []ssh.Signer
|
||||
var agentClient agent.ExtendedAgent
|
||||
conn, err := net.Dial("unix", sshKeywords.IdentityAgent)
|
||||
conn, err := net.Dial("unix", sshKeywords.SshIdentityAgent)
|
||||
if err != nil {
|
||||
log.Printf("Failed to open Identity Agent Socket: %v", err)
|
||||
} else {
|
||||
@ -555,20 +569,20 @@ func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debug
|
||||
|
||||
// exclude gssapi-with-mic and hostbased until implemented
|
||||
authMethodMap := map[string]ssh.AuthMethod{
|
||||
"publickey": ssh.RetryableAuthMethod(publicKeyCallback, len(sshKeywords.IdentityFile)+len(authSockSigners)),
|
||||
"publickey": ssh.RetryableAuthMethod(publicKeyCallback, len(sshKeywords.SshIdentityFile)+len(authSockSigners)),
|
||||
"keyboard-interactive": ssh.RetryableAuthMethod(keyboardInteractive, 1),
|
||||
"password": ssh.RetryableAuthMethod(passwordCallback, 1),
|
||||
}
|
||||
|
||||
// note: batch mode turns off interactive input
|
||||
authMethodActiveMap := map[string]bool{
|
||||
"publickey": sshKeywords.PubkeyAuthentication,
|
||||
"keyboard-interactive": sshKeywords.KbdInteractiveAuthentication && !sshKeywords.BatchMode,
|
||||
"password": sshKeywords.PasswordAuthentication && !sshKeywords.BatchMode,
|
||||
"publickey": sshKeywords.SshPubkeyAuthentication,
|
||||
"keyboard-interactive": sshKeywords.SshKbdInteractiveAuthentication && !sshKeywords.SshBatchMode,
|
||||
"password": sshKeywords.SshPasswordAuthentication && !sshKeywords.SshBatchMode,
|
||||
}
|
||||
|
||||
var authMethods []ssh.AuthMethod
|
||||
for _, authMethodName := range sshKeywords.PreferredAuthentications {
|
||||
for _, authMethodName := range sshKeywords.SshPreferredAuthentications {
|
||||
authMethodActive, ok := authMethodActiveMap[authMethodName]
|
||||
if !ok || !authMethodActive {
|
||||
continue
|
||||
@ -585,9 +599,9 @@ func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debug
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port
|
||||
networkAddr := sshKeywords.SshHostName + ":" + sshKeywords.SshPort
|
||||
return &ssh.ClientConfig{
|
||||
User: sshKeywords.User,
|
||||
User: sshKeywords.SshUser,
|
||||
Auth: authMethods,
|
||||
HostKeyCallback: hostKeyCallback,
|
||||
HostKeyAlgorithms: hostKeyAlgorithms(networkAddr),
|
||||
@ -616,7 +630,7 @@ func connectInternal(ctx context.Context, networkAddr string, clientConfig *ssh.
|
||||
return ssh.NewClient(c, chans, reqs), nil
|
||||
}
|
||||
|
||||
func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.Client, jumpNum int32) (*ssh.Client, int32, error) {
|
||||
func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.Client, jumpNum int32, connFlags *wshrpc.ConnKeywords) (*ssh.Client, int32, error) {
|
||||
debugInfo := &ConnectionDebugInfo{
|
||||
CurrentClient: currentClient,
|
||||
NextOpts: opts,
|
||||
@ -631,12 +645,16 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||
}
|
||||
|
||||
sshKeywords, err := combineSshKeywords(opts, sshConfigKeywords)
|
||||
connFlags.SshUser = opts.SSHUser
|
||||
connFlags.SshHostName = opts.SSHHost
|
||||
connFlags.SshPort = fmt.Sprintf("%d", opts.SSHPort)
|
||||
|
||||
sshKeywords, err := combineSshKeywords(connFlags, sshConfigKeywords)
|
||||
if err != nil {
|
||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||
}
|
||||
|
||||
for _, proxyName := range sshKeywords.ProxyJump {
|
||||
for _, proxyName := range sshKeywords.SshProxyJump {
|
||||
proxyOpts, err := ParseOpts(proxyName)
|
||||
if err != nil {
|
||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||
@ -647,7 +665,8 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
||||
jumpNum += 1
|
||||
}
|
||||
|
||||
debugInfo.CurrentClient, jumpNum, err = ConnectToClient(connCtx, proxyOpts, debugInfo.CurrentClient, jumpNum)
|
||||
// do not apply supplied keywords to proxies - ssh config must be used for that
|
||||
debugInfo.CurrentClient, jumpNum, err = ConnectToClient(connCtx, proxyOpts, debugInfo.CurrentClient, jumpNum, &wshrpc.ConnKeywords{})
|
||||
if err != nil {
|
||||
// do not add a context on a recursive call
|
||||
// (this can cause a recursive nested context that's arbitrarily deep)
|
||||
@ -658,7 +677,7 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
||||
if err != nil {
|
||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||
}
|
||||
networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port
|
||||
networkAddr := sshKeywords.SshHostName + ":" + sshKeywords.SshPort
|
||||
client, err := connectInternal(connCtx, networkAddr, clientConfig, debugInfo.CurrentClient)
|
||||
if err != nil {
|
||||
return client, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||
@ -666,68 +685,51 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
||||
return client, debugInfo.JumpNum, nil
|
||||
}
|
||||
|
||||
type SshKeywords struct {
|
||||
User string
|
||||
HostName string
|
||||
Port string
|
||||
IdentityFile []string
|
||||
BatchMode bool
|
||||
PubkeyAuthentication bool
|
||||
PasswordAuthentication bool
|
||||
KbdInteractiveAuthentication bool
|
||||
PreferredAuthentications []string
|
||||
AddKeysToAgent bool
|
||||
IdentityAgent string
|
||||
ProxyJump []string
|
||||
UserKnownHostsFile []string
|
||||
GlobalKnownHostsFile []string
|
||||
}
|
||||
func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *wshrpc.ConnKeywords) (*wshrpc.ConnKeywords, error) {
|
||||
sshKeywords := &wshrpc.ConnKeywords{}
|
||||
|
||||
func combineSshKeywords(opts *SSHOpts, configKeywords *SshKeywords) (*SshKeywords, error) {
|
||||
sshKeywords := &SshKeywords{}
|
||||
|
||||
if opts.SSHUser != "" {
|
||||
sshKeywords.User = opts.SSHUser
|
||||
} else if configKeywords.User != "" {
|
||||
sshKeywords.User = configKeywords.User
|
||||
if userProvidedOpts.SshUser != "" {
|
||||
sshKeywords.SshUser = userProvidedOpts.SshUser
|
||||
} else if configKeywords.SshUser != "" {
|
||||
sshKeywords.SshUser = configKeywords.SshUser
|
||||
} else {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user for ssh: %+v", err)
|
||||
}
|
||||
sshKeywords.User = user.Username
|
||||
sshKeywords.SshUser = user.Username
|
||||
}
|
||||
|
||||
// we have to check the host value because of the weird way
|
||||
// we store the pattern as the hostname for imported remotes
|
||||
if configKeywords.HostName != "" {
|
||||
sshKeywords.HostName = configKeywords.HostName
|
||||
if configKeywords.SshHostName != "" {
|
||||
sshKeywords.SshHostName = configKeywords.SshHostName
|
||||
} else {
|
||||
sshKeywords.HostName = opts.SSHHost
|
||||
sshKeywords.SshHostName = userProvidedOpts.SshHostName
|
||||
}
|
||||
|
||||
if opts.SSHPort != 0 && opts.SSHPort != 22 {
|
||||
sshKeywords.Port = strconv.Itoa(opts.SSHPort)
|
||||
} else if configKeywords.Port != "" && configKeywords.Port != "22" {
|
||||
sshKeywords.Port = configKeywords.Port
|
||||
if userProvidedOpts.SshPort != "0" && userProvidedOpts.SshPort != "22" {
|
||||
sshKeywords.SshPort = userProvidedOpts.SshPort
|
||||
} else if configKeywords.SshPort != "" && configKeywords.SshPort != "22" {
|
||||
sshKeywords.SshPort = configKeywords.SshPort
|
||||
} else {
|
||||
sshKeywords.Port = "22"
|
||||
sshKeywords.SshPort = "22"
|
||||
}
|
||||
|
||||
sshKeywords.IdentityFile = configKeywords.IdentityFile
|
||||
sshKeywords.SshIdentityFile = append(userProvidedOpts.SshIdentityFile, configKeywords.SshIdentityFile...)
|
||||
|
||||
// these are not officially supported in the waveterm frontend but can be configured
|
||||
// in ssh config files
|
||||
sshKeywords.BatchMode = configKeywords.BatchMode
|
||||
sshKeywords.PubkeyAuthentication = configKeywords.PubkeyAuthentication
|
||||
sshKeywords.PasswordAuthentication = configKeywords.PasswordAuthentication
|
||||
sshKeywords.KbdInteractiveAuthentication = configKeywords.KbdInteractiveAuthentication
|
||||
sshKeywords.PreferredAuthentications = configKeywords.PreferredAuthentications
|
||||
sshKeywords.AddKeysToAgent = configKeywords.AddKeysToAgent
|
||||
sshKeywords.IdentityAgent = configKeywords.IdentityAgent
|
||||
sshKeywords.ProxyJump = configKeywords.ProxyJump
|
||||
sshKeywords.UserKnownHostsFile = configKeywords.UserKnownHostsFile
|
||||
sshKeywords.GlobalKnownHostsFile = configKeywords.GlobalKnownHostsFile
|
||||
sshKeywords.SshBatchMode = configKeywords.SshBatchMode
|
||||
sshKeywords.SshPubkeyAuthentication = configKeywords.SshPubkeyAuthentication
|
||||
sshKeywords.SshPasswordAuthentication = configKeywords.SshPasswordAuthentication
|
||||
sshKeywords.SshKbdInteractiveAuthentication = configKeywords.SshKbdInteractiveAuthentication
|
||||
sshKeywords.SshPreferredAuthentications = configKeywords.SshPreferredAuthentications
|
||||
sshKeywords.SshAddKeysToAgent = configKeywords.SshAddKeysToAgent
|
||||
sshKeywords.SshIdentityAgent = configKeywords.SshIdentityAgent
|
||||
sshKeywords.SshProxyJump = configKeywords.SshProxyJump
|
||||
sshKeywords.SshUserKnownHostsFile = configKeywords.SshUserKnownHostsFile
|
||||
sshKeywords.SshGlobalKnownHostsFile = configKeywords.SshGlobalKnownHostsFile
|
||||
|
||||
return sshKeywords, nil
|
||||
}
|
||||
@ -735,59 +737,60 @@ func combineSshKeywords(opts *SSHOpts, configKeywords *SshKeywords) (*SshKeyword
|
||||
// note that a `var == "yes"` will default to false
|
||||
// but `var != "no"` will default to true
|
||||
// when given unexpected strings
|
||||
func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
||||
func findSshConfigKeywords(hostPattern string) (*wshrpc.ConnKeywords, error) {
|
||||
WaveSshConfigUserSettings().ReloadConfigs()
|
||||
sshKeywords := &SshKeywords{}
|
||||
sshKeywords := &wshrpc.ConnKeywords{}
|
||||
var err error
|
||||
//config := wconfig.ReadFullConfig()
|
||||
|
||||
userRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "User")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.User = trimquotes.TryTrimQuotes(userRaw)
|
||||
sshKeywords.SshUser = trimquotes.TryTrimQuotes(userRaw)
|
||||
|
||||
hostNameRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "HostName")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.HostName = trimquotes.TryTrimQuotes(hostNameRaw)
|
||||
sshKeywords.SshHostName = trimquotes.TryTrimQuotes(hostNameRaw)
|
||||
|
||||
portRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "Port")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.Port = trimquotes.TryTrimQuotes(portRaw)
|
||||
sshKeywords.SshPort = trimquotes.TryTrimQuotes(portRaw)
|
||||
|
||||
identityFileRaw := WaveSshConfigUserSettings().GetAll(hostPattern, "IdentityFile")
|
||||
for i := 0; i < len(identityFileRaw); i++ {
|
||||
identityFileRaw[i] = trimquotes.TryTrimQuotes(identityFileRaw[i])
|
||||
}
|
||||
sshKeywords.IdentityFile = identityFileRaw
|
||||
sshKeywords.SshIdentityFile = identityFileRaw
|
||||
|
||||
batchModeRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "BatchMode")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.BatchMode = (strings.ToLower(trimquotes.TryTrimQuotes(batchModeRaw)) == "yes")
|
||||
sshKeywords.SshBatchMode = (strings.ToLower(trimquotes.TryTrimQuotes(batchModeRaw)) == "yes")
|
||||
|
||||
// we currently do not support host-bound or unbound but will use yes when they are selected
|
||||
pubkeyAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "PubkeyAuthentication")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.PubkeyAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(pubkeyAuthenticationRaw)) != "no")
|
||||
sshKeywords.SshPubkeyAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(pubkeyAuthenticationRaw)) != "no")
|
||||
|
||||
passwordAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "PasswordAuthentication")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.PasswordAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(passwordAuthenticationRaw)) != "no")
|
||||
sshKeywords.SshPasswordAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(passwordAuthenticationRaw)) != "no")
|
||||
|
||||
kbdInteractiveAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "KbdInteractiveAuthentication")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.KbdInteractiveAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(kbdInteractiveAuthenticationRaw)) != "no")
|
||||
sshKeywords.SshKbdInteractiveAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(kbdInteractiveAuthenticationRaw)) != "no")
|
||||
|
||||
// these are parsed as a single string and must be separated
|
||||
// these are case sensitive in openssh so they are here too
|
||||
@ -795,12 +798,12 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.PreferredAuthentications = strings.Split(trimquotes.TryTrimQuotes(preferredAuthenticationsRaw), ",")
|
||||
sshKeywords.SshPreferredAuthentications = strings.Split(trimquotes.TryTrimQuotes(preferredAuthenticationsRaw), ",")
|
||||
addKeysToAgentRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "AddKeysToAgent")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.AddKeysToAgent = (strings.ToLower(trimquotes.TryTrimQuotes(addKeysToAgentRaw)) == "yes")
|
||||
sshKeywords.SshAddKeysToAgent = (strings.ToLower(trimquotes.TryTrimQuotes(addKeysToAgentRaw)) == "yes")
|
||||
|
||||
identityAgentRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "IdentityAgent")
|
||||
if err != nil {
|
||||
@ -815,7 +818,7 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.IdentityAgent = agentPath
|
||||
sshKeywords.SshIdentityAgent = agentPath
|
||||
} else {
|
||||
log.Printf("unable to find SSH_AUTH_SOCK: %v\n", err)
|
||||
}
|
||||
@ -824,7 +827,7 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKeywords.IdentityAgent = agentPath
|
||||
sshKeywords.SshIdentityAgent = agentPath
|
||||
}
|
||||
|
||||
proxyJumpRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "ProxyJump")
|
||||
@ -837,12 +840,12 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
||||
if proxyJumpName == "" || strings.ToLower(proxyJumpName) == "none" {
|
||||
continue
|
||||
}
|
||||
sshKeywords.ProxyJump = append(sshKeywords.ProxyJump, proxyJumpName)
|
||||
sshKeywords.SshProxyJump = append(sshKeywords.SshProxyJump, proxyJumpName)
|
||||
}
|
||||
rawUserKnownHostsFile, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "UserKnownHostsFile")
|
||||
sshKeywords.UserKnownHostsFile = strings.Fields(rawUserKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes
|
||||
sshKeywords.SshUserKnownHostsFile = strings.Fields(rawUserKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes
|
||||
rawGlobalKnownHostsFile, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "GlobalKnownHostsFile")
|
||||
sshKeywords.GlobalKnownHostsFile = strings.Fields(rawGlobalKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes
|
||||
sshKeywords.SshGlobalKnownHostsFile = strings.Fields(rawGlobalKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes
|
||||
|
||||
return sshKeywords, nil
|
||||
}
|
||||
|
@ -237,6 +237,47 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st
|
||||
|
||||
func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
|
||||
client := conn.GetClient()
|
||||
if !conn.WshEnabled.Load() {
|
||||
// no wsh code
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteStdinRead, remoteStdinWriteOurs, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteStdoutReadOurs, remoteStdoutWrite, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pipePty := &PipePty{
|
||||
remoteStdinWrite: remoteStdinWriteOurs,
|
||||
remoteStdoutRead: remoteStdoutReadOurs,
|
||||
}
|
||||
if termSize.Rows == 0 || termSize.Cols == 0 {
|
||||
termSize.Rows = shellutil.DefaultTermRows
|
||||
termSize.Cols = shellutil.DefaultTermCols
|
||||
}
|
||||
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
||||
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
||||
}
|
||||
session.Stdin = remoteStdinRead
|
||||
session.Stdout = remoteStdoutWrite
|
||||
session.Stderr = remoteStdoutWrite
|
||||
|
||||
session.RequestPty("xterm-256color", termSize.Rows, termSize.Cols, nil)
|
||||
sessionWrap := SessionWrap{session, "", pipePty, pipePty}
|
||||
err = session.Shell()
|
||||
if err != nil {
|
||||
pipePty.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
||||
}
|
||||
shellPath := cmdOpts.ShellPath
|
||||
if shellPath == "" {
|
||||
remoteShellPath, err := remote.DetectShell(client)
|
||||
|
@ -14,41 +14,12 @@ import (
|
||||
"github.com/wavetermdev/waveterm/pkg/util/dbutil"
|
||||
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
"github.com/wavetermdev/waveterm/pkg/wstore"
|
||||
)
|
||||
|
||||
const MaxTzNameLen = 50
|
||||
|
||||
type ActivityDisplayType struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
DPR float64 `json:"dpr"`
|
||||
Internal bool `json:"internal,omitempty"`
|
||||
}
|
||||
|
||||
type ActivityUpdate struct {
|
||||
FgMinutes int `json:"fgminutes,omitempty"`
|
||||
ActiveMinutes int `json:"activeminutes,omitempty"`
|
||||
OpenMinutes int `json:"openminutes,omitempty"`
|
||||
NumTabs int `json:"numtabs,omitempty"`
|
||||
NewTab int `json:"newtab,omitempty"`
|
||||
NumBlocks int `json:"numblocks,omitempty"`
|
||||
NumWindows int `json:"numwindows,omitempty"`
|
||||
NumSSHConn int `json:"numsshconn,omitempty"`
|
||||
NumWSLConn int `json:"numwslconn,omitempty"`
|
||||
NumMagnify int `json:"nummagnify,omitempty"`
|
||||
NumPanics int `json:"numpanics,omitempty"`
|
||||
Startup int `json:"startup,omitempty"`
|
||||
Shutdown int `json:"shutdown,omitempty"`
|
||||
SetTabTheme int `json:"settabtheme,omitempty"`
|
||||
BuildTime string `json:"buildtime,omitempty"`
|
||||
Displays []ActivityDisplayType `json:"displays,omitempty"`
|
||||
Renderers map[string]int `json:"renderers,omitempty"`
|
||||
Blocks map[string]int `json:"blocks,omitempty"`
|
||||
WshCmds map[string]int `json:"wshcmds,omitempty"`
|
||||
Conn map[string]int `json:"conn,omitempty"`
|
||||
}
|
||||
|
||||
type ActivityType struct {
|
||||
Day string `json:"day"`
|
||||
Uploaded bool `json:"-"`
|
||||
@ -62,25 +33,25 @@ type ActivityType struct {
|
||||
}
|
||||
|
||||
type TelemetryData struct {
|
||||
ActiveMinutes int `json:"activeminutes"`
|
||||
FgMinutes int `json:"fgminutes"`
|
||||
OpenMinutes int `json:"openminutes"`
|
||||
NumTabs int `json:"numtabs"`
|
||||
NumBlocks int `json:"numblocks,omitempty"`
|
||||
NumWindows int `json:"numwindows,omitempty"`
|
||||
NumSSHConn int `json:"numsshconn,omitempty"`
|
||||
NumWSLConn int `json:"numwslconn,omitempty"`
|
||||
NumMagnify int `json:"nummagnify,omitempty"`
|
||||
NewTab int `json:"newtab"`
|
||||
NumStartup int `json:"numstartup,omitempty"`
|
||||
NumShutdown int `json:"numshutdown,omitempty"`
|
||||
NumPanics int `json:"numpanics,omitempty"`
|
||||
SetTabTheme int `json:"settabtheme,omitempty"`
|
||||
Displays []ActivityDisplayType `json:"displays,omitempty"`
|
||||
Renderers map[string]int `json:"renderers,omitempty"`
|
||||
Blocks map[string]int `json:"blocks,omitempty"`
|
||||
WshCmds map[string]int `json:"wshcmds,omitempty"`
|
||||
Conn map[string]int `json:"conn,omitempty"`
|
||||
ActiveMinutes int `json:"activeminutes"`
|
||||
FgMinutes int `json:"fgminutes"`
|
||||
OpenMinutes int `json:"openminutes"`
|
||||
NumTabs int `json:"numtabs"`
|
||||
NumBlocks int `json:"numblocks,omitempty"`
|
||||
NumWindows int `json:"numwindows,omitempty"`
|
||||
NumSSHConn int `json:"numsshconn,omitempty"`
|
||||
NumWSLConn int `json:"numwslconn,omitempty"`
|
||||
NumMagnify int `json:"nummagnify,omitempty"`
|
||||
NewTab int `json:"newtab"`
|
||||
NumStartup int `json:"numstartup,omitempty"`
|
||||
NumShutdown int `json:"numshutdown,omitempty"`
|
||||
NumPanics int `json:"numpanics,omitempty"`
|
||||
SetTabTheme int `json:"settabtheme,omitempty"`
|
||||
Displays []wshrpc.ActivityDisplayType `json:"displays,omitempty"`
|
||||
Renderers map[string]int `json:"renderers,omitempty"`
|
||||
Blocks map[string]int `json:"blocks,omitempty"`
|
||||
WshCmds map[string]int `json:"wshcmds,omitempty"`
|
||||
Conn map[string]int `json:"conn,omitempty"`
|
||||
}
|
||||
|
||||
func (tdata TelemetryData) Value() (driver.Value, error) {
|
||||
@ -107,7 +78,7 @@ func AutoUpdateChannel() string {
|
||||
}
|
||||
|
||||
// Wraps UpdateCurrentActivity, spawns goroutine, and logs errors
|
||||
func GoUpdateActivityWrap(update ActivityUpdate, debugStr string) {
|
||||
func GoUpdateActivityWrap(update wshrpc.ActivityUpdate, debugStr string) {
|
||||
go func() {
|
||||
defer panichandler.PanicHandlerNoTelemetry("GoUpdateActivityWrap")
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
@ -120,7 +91,7 @@ func GoUpdateActivityWrap(update ActivityUpdate, debugStr string) {
|
||||
}()
|
||||
}
|
||||
|
||||
func UpdateActivity(ctx context.Context, update ActivityUpdate) error {
|
||||
func UpdateActivity(ctx context.Context, update wshrpc.ActivityUpdate) error {
|
||||
now := time.Now()
|
||||
dayStr := daystr.GetCurDayStr()
|
||||
txErr := wstore.WithTx(ctx, func(tx *wstore.TxWrap) error {
|
||||
|
@ -61,7 +61,7 @@ var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
var errorRType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
var anyRType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
var metaRType = reflect.TypeOf((*waveobj.MetaMapType)(nil)).Elem()
|
||||
var metaSettingsType = reflect.TypeOf((*wconfig.MetaSettingsType)(nil)).Elem()
|
||||
var metaSettingsType = reflect.TypeOf((*wshrpc.MetaSettingsType)(nil)).Elem()
|
||||
var uiContextRType = reflect.TypeOf((*waveobj.UIContext)(nil)).Elem()
|
||||
var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
|
||||
var updatesRtnRType = reflect.TypeOf(waveobj.UpdatesRtnType{})
|
||||
|
@ -25,6 +25,8 @@ type UserInputRequest struct {
|
||||
TimeoutMs int `json:"timeoutms"`
|
||||
CheckBoxMsg string `json:"checkboxmsg"`
|
||||
PublicText bool `json:"publictext"`
|
||||
OkLabel string `json:"oklabel,omitempty"`
|
||||
CancelLabel string `json:"cancellabel,omitempty"`
|
||||
}
|
||||
|
||||
type UserInputResponse struct {
|
||||
|
@ -7,6 +7,7 @@
|
||||
"autoupdate:installonquit": true,
|
||||
"autoupdate:intervalms": 3600000,
|
||||
"conn:askbeforewshinstall": true,
|
||||
"conn:wshenabled": true,
|
||||
"editor:minimapenabled": true,
|
||||
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
|
||||
"web:defaultsearch": "https://www.google.com/search?q={query}",
|
||||
|
@ -71,5 +71,6 @@ const (
|
||||
|
||||
ConfigKey_ConnClear = "conn:*"
|
||||
ConfigKey_ConnAskBeforeWshInstall = "conn:askbeforewshinstall"
|
||||
ConfigKey_ConnWshEnabled = "conn:wshenabled"
|
||||
)
|
||||
|
||||
|
@ -19,9 +19,11 @@ import (
|
||||
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||
"github.com/wavetermdev/waveterm/pkg/wconfig/defaultconfig"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
)
|
||||
|
||||
const SettingsFile = "settings.json"
|
||||
const ConnectionsFile = "connections.json"
|
||||
|
||||
const AnySchema = `
|
||||
{
|
||||
@ -30,25 +32,6 @@ const AnySchema = `
|
||||
}
|
||||
`
|
||||
|
||||
type MetaSettingsType struct {
|
||||
waveobj.MetaMapType
|
||||
}
|
||||
|
||||
func (m *MetaSettingsType) UnmarshalJSON(data []byte) error {
|
||||
var metaMap waveobj.MetaMapType
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.UseNumber()
|
||||
if err := decoder.Decode(&metaMap); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = MetaSettingsType{MetaMapType: metaMap}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MetaSettingsType) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(m.MetaMapType)
|
||||
}
|
||||
|
||||
type SettingsType struct {
|
||||
AiClear bool `json:"ai:*,omitempty"`
|
||||
AiPreset string `json:"ai:preset,omitempty"`
|
||||
@ -115,6 +98,7 @@ type SettingsType struct {
|
||||
|
||||
ConnClear bool `json:"conn:*,omitempty"`
|
||||
ConnAskBeforeWshInstall bool `json:"conn:askbeforewshinstall,omitempty"`
|
||||
ConnWshEnabled bool `json:"conn:wshenabled,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigError struct {
|
||||
@ -123,12 +107,14 @@ type ConfigError struct {
|
||||
}
|
||||
|
||||
type FullConfigType struct {
|
||||
Settings SettingsType `json:"settings" merge:"meta"`
|
||||
MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"`
|
||||
Widgets map[string]WidgetConfigType `json:"widgets"`
|
||||
Presets map[string]waveobj.MetaMapType `json:"presets"`
|
||||
TermThemes map[string]TermThemeType `json:"termthemes"`
|
||||
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
||||
Settings SettingsType `json:"settings" merge:"meta"`
|
||||
MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"`
|
||||
DefaultWidgets map[string]WidgetConfigType `json:"defaultwidgets"`
|
||||
Widgets map[string]WidgetConfigType `json:"widgets"`
|
||||
Presets map[string]waveobj.MetaMapType `json:"presets"`
|
||||
TermThemes map[string]TermThemeType `json:"termthemes"`
|
||||
Connections map[string]wshrpc.ConnKeywords `json:"connections"`
|
||||
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
||||
}
|
||||
|
||||
func goBackWS(barr []byte, offset int) int {
|
||||
@ -401,12 +387,12 @@ func reindentJson(barr []byte, indentStr string) []byte {
|
||||
if barr[0] != '{' && barr[0] != '[' {
|
||||
return barr
|
||||
}
|
||||
if bytes.Contains(barr, []byte("\n")) {
|
||||
if !bytes.Contains(barr, []byte("\n")) {
|
||||
return barr
|
||||
}
|
||||
outputLines := bytes.Split(barr, []byte("\n"))
|
||||
for i, line := range outputLines {
|
||||
if i == 0 || i == len(outputLines)-1 {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
outputLines[i] = append([]byte(indentStr), line...)
|
||||
@ -509,6 +495,25 @@ func SetBaseConfigValue(toMerge waveobj.MetaMapType) error {
|
||||
return WriteWaveHomeConfigFile(SettingsFile, m)
|
||||
}
|
||||
|
||||
func SetConnectionsConfigValue(connName string, toMerge waveobj.MetaMapType) error {
|
||||
m, cerrs := ReadWaveHomeConfigFile(ConnectionsFile)
|
||||
if len(cerrs) > 0 {
|
||||
return fmt.Errorf("error reading config file: %v", cerrs[0])
|
||||
}
|
||||
if m == nil {
|
||||
m = make(waveobj.MetaMapType)
|
||||
}
|
||||
connData := m.GetMap(connName)
|
||||
if connData == nil {
|
||||
connData = make(waveobj.MetaMapType)
|
||||
}
|
||||
for configKey, val := range toMerge {
|
||||
connData[configKey] = val
|
||||
}
|
||||
m[connName] = connData
|
||||
return WriteWaveHomeConfigFile(ConnectionsFile, m)
|
||||
}
|
||||
|
||||
type WidgetConfigType struct {
|
||||
DisplayOrder float64 `json:"display:order,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
"github.com/wavetermdev/waveterm/pkg/wstore"
|
||||
)
|
||||
|
||||
@ -119,7 +120,7 @@ func CreateTab(ctx context.Context, windowId string, tabName string, activateTab
|
||||
return "", fmt.Errorf("error setting active tab: %w", err)
|
||||
}
|
||||
}
|
||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{NewTab: 1}, "createtab")
|
||||
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{NewTab: 1}, "createtab")
|
||||
return tab.OID, nil
|
||||
}
|
||||
|
||||
@ -282,7 +283,7 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef,
|
||||
}
|
||||
tctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancelFn()
|
||||
telemetry.UpdateActivity(tctx, telemetry.ActivityUpdate{
|
||||
telemetry.UpdateActivity(tctx, wshrpc.ActivityUpdate{
|
||||
Renderers: map[string]int{blockView: 1},
|
||||
})
|
||||
}()
|
||||
|
@ -9,14 +9,12 @@ import (
|
||||
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||
"github.com/wavetermdev/waveterm/pkg/vdom"
|
||||
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
||||
)
|
||||
|
||||
// command "activity", wshserver.ActivityCommand
|
||||
func ActivityCommand(w *wshutil.WshRpc, data telemetry.ActivityUpdate, opts *wshrpc.RpcOpts) error {
|
||||
func ActivityCommand(w *wshutil.WshRpc, data wshrpc.ActivityUpdate, opts *wshrpc.RpcOpts) error {
|
||||
_, err := sendRpcRequestCallHelper[any](w, "activity", data, opts)
|
||||
return err
|
||||
}
|
||||
@ -40,7 +38,7 @@ func BlockInfoCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (*ws
|
||||
}
|
||||
|
||||
// command "connconnect", wshserver.ConnConnectCommand
|
||||
func ConnConnectCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
|
||||
func ConnConnectCommand(w *wshutil.WshRpc, data wshrpc.ConnRequest, opts *wshrpc.RpcOpts) error {
|
||||
_, err := sendRpcRequestCallHelper[any](w, "connconnect", data, opts)
|
||||
return err
|
||||
}
|
||||
@ -290,7 +288,7 @@ func RouteUnannounceCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) error {
|
||||
}
|
||||
|
||||
// command "setconfig", wshserver.SetConfigCommand
|
||||
func SetConfigCommand(w *wshutil.WshRpc, data wconfig.MetaSettingsType, opts *wshrpc.RpcOpts) error {
|
||||
func SetConfigCommand(w *wshutil.WshRpc, data wshrpc.MetaSettingsType, opts *wshrpc.RpcOpts) error {
|
||||
_, err := sendRpcRequestCallHelper[any](w, "setconfig", data, opts)
|
||||
return err
|
||||
}
|
||||
|
@ -5,17 +5,17 @@
|
||||
package wshrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/wavetermdev/waveterm/pkg/filestore"
|
||||
"github.com/wavetermdev/waveterm/pkg/ijson"
|
||||
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
||||
"github.com/wavetermdev/waveterm/pkg/vdom"
|
||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||
)
|
||||
|
||||
@ -133,11 +133,11 @@ type WshRpcInterface interface {
|
||||
StreamWaveAiCommand(ctx context.Context, request OpenAiStreamRequest) chan RespOrErrorUnion[OpenAIPacketType]
|
||||
StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData]
|
||||
TestCommand(ctx context.Context, data string) error
|
||||
SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error
|
||||
SetConfigCommand(ctx context.Context, data MetaSettingsType) error
|
||||
BlockInfoCommand(ctx context.Context, blockId string) (*BlockInfoData, error)
|
||||
WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)
|
||||
WshActivityCommand(ct context.Context, data map[string]int) error
|
||||
ActivityCommand(ctx context.Context, data telemetry.ActivityUpdate) error
|
||||
ActivityCommand(ctx context.Context, data ActivityUpdate) error
|
||||
GetVarCommand(ctx context.Context, data CommandVarData) (*CommandVarResponseData, error)
|
||||
SetVarCommand(ctx context.Context, data CommandVarData) error
|
||||
|
||||
@ -146,7 +146,7 @@ type WshRpcInterface interface {
|
||||
WslStatusCommand(ctx context.Context) ([]ConnStatus, error)
|
||||
ConnEnsureCommand(ctx context.Context, connName string) error
|
||||
ConnReinstallWshCommand(ctx context.Context, connName string) error
|
||||
ConnConnectCommand(ctx context.Context, connName string) error
|
||||
ConnConnectCommand(ctx context.Context, connRequest ConnRequest) error
|
||||
ConnDisconnectCommand(ctx context.Context, connName string) error
|
||||
ConnListCommand(ctx context.Context) ([]string, error)
|
||||
WslListCommand(ctx context.Context) ([]string, error)
|
||||
@ -440,6 +440,31 @@ type CommandRemoteWriteFileData struct {
|
||||
CreateMode os.FileMode `json:"createmode,omitempty"`
|
||||
}
|
||||
|
||||
type ConnKeywords struct {
|
||||
WshEnabled *bool `json:"wshenabled,omitempty"`
|
||||
AskBeforeWshInstall *bool `json:"askbeforewshinstall,omitempty"`
|
||||
|
||||
SshUser string `json:"ssh:user,omitempty"`
|
||||
SshHostName string `json:"ssh:hostname,omitempty"`
|
||||
SshPort string `json:"ssh:port,omitempty"`
|
||||
SshIdentityFile []string `json:"ssh:identityfile,omitempty"`
|
||||
SshBatchMode bool `json:"ssh:batchmode,omitempty"`
|
||||
SshPubkeyAuthentication bool `json:"ssh:pubkeyauthentication,omitempty"`
|
||||
SshPasswordAuthentication bool `json:"ssh:passwordauthentication,omitempty"`
|
||||
SshKbdInteractiveAuthentication bool `json:"ssh:kbdinteractiveauthentication,omitempty"`
|
||||
SshPreferredAuthentications []string `json:"ssh:preferredauthentications,omitempty"`
|
||||
SshAddKeysToAgent bool `json:"ssh:addkeystoagent,omitempty"`
|
||||
SshIdentityAgent string `json:"ssh:identityagent,omitempty"`
|
||||
SshProxyJump []string `json:"ssh:proxyjump,omitempty"`
|
||||
SshUserKnownHostsFile []string `json:"ssh:userknownhostsfile,omitempty"`
|
||||
SshGlobalKnownHostsFile []string `json:"ssh:globalknownhostsfile,omitempty"`
|
||||
}
|
||||
|
||||
type ConnRequest struct {
|
||||
Host string `json:"host"`
|
||||
Keywords ConnKeywords `json:"keywords,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
TimeSeries_Cpu = "cpu"
|
||||
)
|
||||
@ -449,8 +474,28 @@ type TimeSeriesData struct {
|
||||
Values map[string]float64 `json:"values"`
|
||||
}
|
||||
|
||||
type MetaSettingsType struct {
|
||||
waveobj.MetaMapType
|
||||
}
|
||||
|
||||
func (m *MetaSettingsType) UnmarshalJSON(data []byte) error {
|
||||
var metaMap waveobj.MetaMapType
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.UseNumber()
|
||||
if err := decoder.Decode(&metaMap); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = MetaSettingsType{MetaMapType: metaMap}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MetaSettingsType) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(m.MetaMapType)
|
||||
}
|
||||
|
||||
type ConnStatus struct {
|
||||
Status string `json:"status"`
|
||||
WshEnabled bool `json:"wshenabled"`
|
||||
Connection string `json:"connection"`
|
||||
Connected bool `json:"connected"`
|
||||
HasConnected bool `json:"hasconnected"` // true if it has *ever* connected successfully
|
||||
@ -522,3 +567,33 @@ type CommandVarResponseData struct {
|
||||
Val string `json:"val"`
|
||||
Exists bool `json:"exists"`
|
||||
}
|
||||
|
||||
type ActivityDisplayType struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
DPR float64 `json:"dpr"`
|
||||
Internal bool `json:"internal,omitempty"`
|
||||
}
|
||||
|
||||
type ActivityUpdate struct {
|
||||
FgMinutes int `json:"fgminutes,omitempty"`
|
||||
ActiveMinutes int `json:"activeminutes,omitempty"`
|
||||
OpenMinutes int `json:"openminutes,omitempty"`
|
||||
NumTabs int `json:"numtabs,omitempty"`
|
||||
NewTab int `json:"newtab,omitempty"`
|
||||
NumBlocks int `json:"numblocks,omitempty"`
|
||||
NumWindows int `json:"numwindows,omitempty"`
|
||||
NumSSHConn int `json:"numsshconn,omitempty"`
|
||||
NumWSLConn int `json:"numwslconn,omitempty"`
|
||||
NumMagnify int `json:"nummagnify,omitempty"`
|
||||
NumPanics int `json:"numpanics,omitempty"`
|
||||
Startup int `json:"startup,omitempty"`
|
||||
Shutdown int `json:"shutdown,omitempty"`
|
||||
SetTabTheme int `json:"settabtheme,omitempty"`
|
||||
BuildTime string `json:"buildtime,omitempty"`
|
||||
Displays []ActivityDisplayType `json:"displays,omitempty"`
|
||||
Renderers map[string]int `json:"renderers,omitempty"`
|
||||
Blocks map[string]int `json:"blocks,omitempty"`
|
||||
WshCmds map[string]int `json:"wshcmds,omitempty"`
|
||||
Conn map[string]int `json:"conn,omitempty"`
|
||||
}
|
||||
|
@ -587,7 +587,7 @@ func (ws *WshServer) EventReadHistoryCommand(ctx context.Context, data wshrpc.Co
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (ws *WshServer) SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error {
|
||||
func (ws *WshServer) SetConfigCommand(ctx context.Context, data wshrpc.MetaSettingsType) error {
|
||||
log.Printf("SETCONFIG: %v\n", data)
|
||||
return wconfig.SetBaseConfigValue(data.MetaMapType)
|
||||
}
|
||||
@ -623,14 +623,15 @@ func (ws *WshServer) ConnDisconnectCommand(ctx context.Context, connName string)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing connection name: %w", err)
|
||||
}
|
||||
conn := conncontroller.GetConn(ctx, connOpts, false)
|
||||
conn := conncontroller.GetConn(ctx, connOpts, false, &wshrpc.ConnKeywords{})
|
||||
if conn == nil {
|
||||
return fmt.Errorf("connection not found: %s", connName)
|
||||
}
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func (ws *WshServer) ConnConnectCommand(ctx context.Context, connName string) error {
|
||||
func (ws *WshServer) ConnConnectCommand(ctx context.Context, connRequest wshrpc.ConnRequest) error {
|
||||
connName := connRequest.Host
|
||||
if strings.HasPrefix(connName, "wsl://") {
|
||||
distroName := strings.TrimPrefix(connName, "wsl://")
|
||||
conn := wsl.GetWslConn(ctx, distroName, false)
|
||||
@ -643,11 +644,11 @@ func (ws *WshServer) ConnConnectCommand(ctx context.Context, connName string) er
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing connection name: %w", err)
|
||||
}
|
||||
conn := conncontroller.GetConn(ctx, connOpts, false)
|
||||
conn := conncontroller.GetConn(ctx, connOpts, false, &connRequest.Keywords)
|
||||
if conn == nil {
|
||||
return fmt.Errorf("connection not found: %s", connName)
|
||||
}
|
||||
return conn.Connect(ctx)
|
||||
return conn.Connect(ctx, &connRequest.Keywords)
|
||||
}
|
||||
|
||||
func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, connName string) error {
|
||||
@ -663,7 +664,7 @@ func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, connName strin
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing connection name: %w", err)
|
||||
}
|
||||
conn := conncontroller.GetConn(ctx, connOpts, false)
|
||||
conn := conncontroller.GetConn(ctx, connOpts, false, &wshrpc.ConnKeywords{})
|
||||
if conn == nil {
|
||||
return fmt.Errorf("connection not found: %s", connName)
|
||||
}
|
||||
@ -753,14 +754,14 @@ func (ws *WshServer) WshActivityCommand(ctx context.Context, data map[string]int
|
||||
delete(data, key)
|
||||
}
|
||||
}
|
||||
activityUpdate := telemetry.ActivityUpdate{
|
||||
activityUpdate := wshrpc.ActivityUpdate{
|
||||
WshCmds: data,
|
||||
}
|
||||
telemetry.GoUpdateActivityWrap(activityUpdate, "wsh-activity")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ws *WshServer) ActivityCommand(ctx context.Context, activity telemetry.ActivityUpdate) error {
|
||||
func (ws *WshServer) ActivityCommand(ctx context.Context, activity wshrpc.ActivityUpdate) error {
|
||||
telemetry.GoUpdateActivityWrap(activity, "wshrpc-activity")
|
||||
return nil
|
||||
}
|
||||
|
@ -394,7 +394,7 @@ func (conn *WslConn) Connect(ctx context.Context) error {
|
||||
conn.Status = Status_Error
|
||||
conn.Error = err.Error()
|
||||
conn.close_nolock()
|
||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
||||
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||
Conn: map[string]int{"wsl:connecterror": 1},
|
||||
}, "wsl-connconnect")
|
||||
} else {
|
||||
@ -403,7 +403,7 @@ func (conn *WslConn) Connect(ctx context.Context) error {
|
||||
if conn.ActiveConnNum == 0 {
|
||||
conn.ActiveConnNum = int(activeConnCounter.Add(1))
|
||||
}
|
||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
||||
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||
Conn: map[string]int{"wsl:connect": 1},
|
||||
}, "wsl-connconnect")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user