mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-25 17:18:02 +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/wshutil",
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc",
|
"github.com/wavetermdev/waveterm/pkg/wshrpc",
|
||||||
"github.com/wavetermdev/waveterm/pkg/waveobj",
|
"github.com/wavetermdev/waveterm/pkg/waveobj",
|
||||||
"github.com/wavetermdev/waveterm/pkg/wconfig",
|
|
||||||
"github.com/wavetermdev/waveterm/pkg/wps",
|
"github.com/wavetermdev/waveterm/pkg/wps",
|
||||||
"github.com/wavetermdev/waveterm/pkg/vdom",
|
"github.com/wavetermdev/waveterm/pkg/vdom",
|
||||||
"github.com/wavetermdev/waveterm/pkg/telemetry",
|
|
||||||
})
|
})
|
||||||
wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
|
wshDeclMap := wshrpc.GenerateWshCommandDeclMap()
|
||||||
for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {
|
for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) {
|
||||||
|
@ -113,7 +113,7 @@ func telemetryLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func panicTelemetryHandler() {
|
func panicTelemetryHandler() {
|
||||||
activity := telemetry.ActivityUpdate{NumPanics: 1}
|
activity := wshrpc.ActivityUpdate{NumPanics: 1}
|
||||||
err := telemetry.UpdateActivity(context.Background(), activity)
|
err := telemetry.UpdateActivity(context.Background(), activity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error updating activity (panicTelemetryHandler): %v\n", err)
|
log.Printf("error updating activity (panicTelemetryHandler): %v\n", err)
|
||||||
@ -137,7 +137,7 @@ func sendTelemetryWrapper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func beforeSendActivityUpdate(ctx context.Context) {
|
func beforeSendActivityUpdate(ctx context.Context) {
|
||||||
activity := telemetry.ActivityUpdate{}
|
activity := wshrpc.ActivityUpdate{}
|
||||||
activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx)
|
activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx)
|
||||||
activity.NumBlocks, _ = wstore.DBGetCount[*waveobj.Block](ctx)
|
activity.NumBlocks, _ = wstore.DBGetCount[*waveobj.Block](ctx)
|
||||||
activity.Blocks, _ = wstore.DBGetBlockViewCounts(ctx)
|
activity.Blocks, _ = wstore.DBGetBlockViewCounts(ctx)
|
||||||
@ -153,7 +153,7 @@ func beforeSendActivityUpdate(ctx context.Context) {
|
|||||||
func startupActivityUpdate() {
|
func startupActivityUpdate() {
|
||||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancelFn()
|
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)
|
err := telemetry.UpdateActivity(ctx, activity) // set at least one record into activity (don't use go routine wrap here)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error updating startup activity: %v\n", err)
|
log.Printf("error updating startup activity: %v\n", err)
|
||||||
@ -163,7 +163,7 @@ func startupActivityUpdate() {
|
|||||||
func shutdownActivityUpdate() {
|
func shutdownActivityUpdate() {
|
||||||
ctx, cancelFn := context.WithTimeout(context.Background(), 1*time.Second)
|
ctx, cancelFn := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
defer cancelFn()
|
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)
|
err := telemetry.UpdateActivity(ctx, activity) // do NOT use the go routine wrap here (this needs to be synchronous)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error updating shutdown activity: %v\n", err)
|
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 {
|
if err := validateConnectionName(connName); err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("connecting connection: %w", err)
|
return fmt.Errorf("connecting connection: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||||||
)
|
)
|
||||||
@ -34,7 +33,7 @@ func setConfigRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
commandData := wconfig.MetaSettingsType{MetaMapType: meta}
|
commandData := wshrpc.MetaSettingsType{MetaMapType: meta}
|
||||||
err = wshclient.SetConfigCommand(RpcClient, commandData, &wshrpc.RpcOpts{Timeout: 2000})
|
err = wshclient.SetConfigCommand(RpcClient, commandData, &wshrpc.RpcOpts{Timeout: 2000})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting config: %w", err)
|
return fmt.Errorf("setting config: %w", err)
|
||||||
|
@ -12,6 +12,8 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var identityFiles []string
|
||||||
|
|
||||||
var sshCmd = &cobra.Command{
|
var sshCmd = &cobra.Command{
|
||||||
Use: "ssh",
|
Use: "ssh",
|
||||||
Short: "connect this terminal to a remote host",
|
Short: "connect this terminal to a remote host",
|
||||||
@ -21,6 +23,7 @@ var sshCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
sshCmd.Flags().StringArrayVarP(&identityFiles, "identityfile", "i", []string{}, "add an identity file for publickey authentication")
|
||||||
rootCmd.AddCommand(sshCmd)
|
rootCmd.AddCommand(sshCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +37,16 @@ func sshRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
|||||||
if blockId == "" {
|
if blockId == "" {
|
||||||
return fmt.Errorf("cannot determine blockid (not in JWT)")
|
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{
|
data := wshrpc.CommandSetMetaData{
|
||||||
ORef: waveobj.MakeORef(waveobj.OType_Block, blockId),
|
ORef: waveobj.MakeORef(waveobj.OType_Block, blockId),
|
||||||
Meta: map[string]any{
|
Meta: map[string]any{
|
||||||
|
@ -341,7 +341,7 @@ const ConnStatusOverlay = React.memo(
|
|||||||
}, [width, connStatus, setShowError]);
|
}, [width, connStatus, setShowError]);
|
||||||
|
|
||||||
const handleTryReconnect = React.useCallback(() => {
|
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));
|
prtn.catch((e) => console.log("error reconnecting", connName, e));
|
||||||
}, [connName]);
|
}, [connName]);
|
||||||
|
|
||||||
@ -673,7 +673,11 @@ const ChangeConnectionBlockModal = React.memo(
|
|||||||
label: `Reconnect to ${connStatus.connection}`,
|
label: `Reconnect to ${connStatus.connection}`,
|
||||||
value: "",
|
value: "",
|
||||||
onSelect: async (_: string) => {
|
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));
|
prtn.catch((e) => console.log("error reconnecting", connStatus.connection, e));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -44,6 +44,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.userinput-checkbox-container {
|
.userinput-checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.userinput-checkbox-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@ -53,3 +58,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
const [countdown, setCountdown] = useState(Math.floor(userInputRequest.timeoutms / 1000));
|
const [countdown, setCountdown] = useState(Math.floor(userInputRequest.timeoutms / 1000));
|
||||||
const checkboxRef = useRef<HTMLInputElement>();
|
const checkboxRef = useRef<HTMLInputElement>();
|
||||||
|
|
||||||
const handleSendCancel = useCallback(() => {
|
const handleSendErrResponse = useCallback(() => {
|
||||||
UserInputService.SendUserInputResponse({
|
UserInputService.SendUserInputResponse({
|
||||||
type: "userinputresp",
|
type: "userinputresp",
|
||||||
requestid: userInputRequest.requestid,
|
requestid: userInputRequest.requestid,
|
||||||
@ -29,20 +29,24 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
type: "userinputresp",
|
type: "userinputresp",
|
||||||
requestid: userInputRequest.requestid,
|
requestid: userInputRequest.requestid,
|
||||||
text: responseText,
|
text: responseText,
|
||||||
checkboxstat: checkboxRef.current?.checked ?? false,
|
checkboxstat: checkboxRef?.current?.checked ?? false,
|
||||||
});
|
});
|
||||||
modalsModel.popModal();
|
modalsModel.popModal();
|
||||||
}, [responseText, userInputRequest]);
|
}, [responseText, userInputRequest]);
|
||||||
|
console.log("bar");
|
||||||
|
|
||||||
const handleSendConfirm = useCallback(() => {
|
const handleSendConfirm = useCallback(
|
||||||
|
(response: boolean) => {
|
||||||
UserInputService.SendUserInputResponse({
|
UserInputService.SendUserInputResponse({
|
||||||
type: "userinputresp",
|
type: "userinputresp",
|
||||||
requestid: userInputRequest.requestid,
|
requestid: userInputRequest.requestid,
|
||||||
confirm: true,
|
confirm: response,
|
||||||
checkboxstat: checkboxRef.current?.checked ?? false,
|
checkboxstat: checkboxRef?.current?.checked ?? false,
|
||||||
});
|
});
|
||||||
modalsModel.popModal();
|
modalsModel.popModal();
|
||||||
}, [userInputRequest]);
|
},
|
||||||
|
[userInputRequest]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
switch (userInputRequest.responsetype) {
|
switch (userInputRequest.responsetype) {
|
||||||
@ -50,15 +54,16 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
handleSendText();
|
handleSendText();
|
||||||
break;
|
break;
|
||||||
case "confirm":
|
case "confirm":
|
||||||
handleSendConfirm();
|
handleSendConfirm(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [handleSendConfirm, handleSendText, userInputRequest.responsetype]);
|
}, [handleSendConfirm, handleSendText, userInputRequest.responsetype]);
|
||||||
|
console.log("baz");
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(waveEvent: WaveKeyboardEvent): boolean => {
|
(waveEvent: WaveKeyboardEvent): boolean => {
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
|
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
|
||||||
handleSendCancel();
|
handleSendErrResponse();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
||||||
@ -66,7 +71,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleSendCancel, handleSubmit]
|
[handleSendErrResponse, handleSubmit]
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryText = useMemo(() => {
|
const queryText = useMemo(() => {
|
||||||
@ -75,6 +80,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
}
|
}
|
||||||
return <span className="userinput-text">{userInputRequest.querytext}</span>;
|
return <span className="userinput-text">{userInputRequest.querytext}</span>;
|
||||||
}, [userInputRequest.markdown, userInputRequest.querytext]);
|
}, [userInputRequest.markdown, userInputRequest.querytext]);
|
||||||
|
console.log("foobarbaz");
|
||||||
|
|
||||||
const inputBox = useMemo(() => {
|
const inputBox = useMemo(() => {
|
||||||
if (userInputRequest.responsetype === "confirm") {
|
if (userInputRequest.responsetype === "confirm") {
|
||||||
@ -92,6 +98,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [userInputRequest.responsetype, userInputRequest.publictext, responseText, handleKeyDown, setResponseText]);
|
}, [userInputRequest.responsetype, userInputRequest.publictext, responseText, handleKeyDown, setResponseText]);
|
||||||
|
console.log("mem1");
|
||||||
|
|
||||||
const optionalCheckbox = useMemo(() => {
|
const optionalCheckbox = useMemo(() => {
|
||||||
if (userInputRequest.checkboxmsg == "") {
|
if (userInputRequest.checkboxmsg == "") {
|
||||||
@ -99,6 +106,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="userinput-checkbox-container">
|
<div className="userinput-checkbox-container">
|
||||||
|
<div className="userinput-checkbox-row">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={`uicheckbox-${userInputRequest.requestid}`}
|
id={`uicheckbox-${userInputRequest.requestid}`}
|
||||||
@ -107,14 +115,16 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
/>
|
/>
|
||||||
<label htmlFor={`uicheckbox-${userInputRequest.requestid}}`}>{userInputRequest.checkboxmsg}</label>
|
<label htmlFor={`uicheckbox-${userInputRequest.requestid}}`}>{userInputRequest.checkboxmsg}</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
console.log("mem2");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
if (countdown <= 0) {
|
if (countdown <= 0) {
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
handleSendCancel();
|
handleSendErrResponse();
|
||||||
}, 300);
|
}, 300);
|
||||||
} else {
|
} else {
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
@ -123,9 +133,28 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
}
|
}
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}, [countdown]);
|
}, [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 (
|
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-header">{userInputRequest.title + ` (${countdown}s)`}</div>
|
||||||
<div className="userinput-body">
|
<div className="userinput-body">
|
||||||
{queryText}
|
{queryText}
|
||||||
|
@ -28,7 +28,7 @@ class RpcApiType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// command "connconnect" [call]
|
// 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);
|
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 {
|
declare global {
|
||||||
|
|
||||||
// telemetry.ActivityDisplayType
|
// wshrpc.ActivityDisplayType
|
||||||
type ActivityDisplayType = {
|
type ActivityDisplayType = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@ -13,7 +13,7 @@ declare global {
|
|||||||
internal?: boolean;
|
internal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// telemetry.ActivityUpdate
|
// wshrpc.ActivityUpdate
|
||||||
type ActivityUpdate = {
|
type ActivityUpdate = {
|
||||||
fgminutes?: number;
|
fgminutes?: number;
|
||||||
activeminutes?: number;
|
activeminutes?: number;
|
||||||
@ -275,9 +275,36 @@ declare global {
|
|||||||
err: string;
|
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
|
// wshrpc.ConnStatus
|
||||||
type ConnStatus = {
|
type ConnStatus = {
|
||||||
status: string;
|
status: string;
|
||||||
|
wshenabled: boolean;
|
||||||
connection: string;
|
connection: string;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
hasconnected: boolean;
|
hasconnected: boolean;
|
||||||
@ -337,9 +364,11 @@ declare global {
|
|||||||
type FullConfigType = {
|
type FullConfigType = {
|
||||||
settings: SettingsType;
|
settings: SettingsType;
|
||||||
mimetypes: {[key: string]: MimeTypeConfigType};
|
mimetypes: {[key: string]: MimeTypeConfigType};
|
||||||
|
defaultwidgets: {[key: string]: WidgetConfigType};
|
||||||
widgets: {[key: string]: WidgetConfigType};
|
widgets: {[key: string]: WidgetConfigType};
|
||||||
presets: {[key: string]: MetaType};
|
presets: {[key: string]: MetaType};
|
||||||
termthemes: {[key: string]: TermThemeType};
|
termthemes: {[key: string]: TermThemeType};
|
||||||
|
connections: {[key: string]: ConnKeywords};
|
||||||
configerrors: ConfigError[];
|
configerrors: ConfigError[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -608,6 +637,7 @@ declare global {
|
|||||||
"telemetry:enabled"?: boolean;
|
"telemetry:enabled"?: boolean;
|
||||||
"conn:*"?: boolean;
|
"conn:*"?: boolean;
|
||||||
"conn:askbeforewshinstall"?: boolean;
|
"conn:askbeforewshinstall"?: boolean;
|
||||||
|
"conn:wshenabled"?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// waveobj.StickerClickOptsType
|
// waveobj.StickerClickOptsType
|
||||||
@ -701,6 +731,8 @@ declare global {
|
|||||||
timeoutms: number;
|
timeoutms: number;
|
||||||
checkboxmsg: string;
|
checkboxmsg: string;
|
||||||
publictext: boolean;
|
publictext: boolean;
|
||||||
|
oklabel?: string;
|
||||||
|
cancellabel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// userinput.UserInputResponse
|
// userinput.UserInputResponse
|
||||||
|
@ -296,7 +296,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn := conncontroller.GetConn(credentialCtx, opts, false)
|
conn := conncontroller.GetConn(credentialCtx, opts, false, &wshrpc.ConnKeywords{})
|
||||||
connStatus := conn.DeriveConnStatus()
|
connStatus := conn.DeriveConnStatus()
|
||||||
if connStatus.Status != conncontroller.Status_Connected {
|
if connStatus.Status != conncontroller.Status_Connected {
|
||||||
return fmt.Errorf("not connected, cannot start shellproc")
|
return fmt.Errorf("not connected, cannot start shellproc")
|
||||||
@ -538,7 +538,7 @@ func CheckConnStatus(blockId string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing connection name: %w", err)
|
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()
|
connStatus := conn.DeriveConnStatus()
|
||||||
if connStatus.Status != conncontroller.Status_Connected {
|
if connStatus.Status != conncontroller.Status_Connected {
|
||||||
return fmt.Errorf("not connected: %s", connStatus.Status)
|
return fmt.Errorf("not connected: %s", connStatus.Status)
|
||||||
|
@ -52,6 +52,7 @@ var activeConnCounter = &atomic.Int32{}
|
|||||||
type SSHConn struct {
|
type SSHConn struct {
|
||||||
Lock *sync.Mutex
|
Lock *sync.Mutex
|
||||||
Status string
|
Status string
|
||||||
|
WshEnabled *atomic.Bool
|
||||||
Opts *remote.SSHOpts
|
Opts *remote.SSHOpts
|
||||||
Client *ssh.Client
|
Client *ssh.Client
|
||||||
SockName string
|
SockName string
|
||||||
@ -290,6 +291,12 @@ type WshInstallOpts struct {
|
|||||||
NoUserPrompt bool
|
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 {
|
func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName string, opts *WshInstallOpts) error {
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &WshInstallOpts{}
|
opts = &WshInstallOpts{}
|
||||||
@ -325,12 +332,23 @@ func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName s
|
|||||||
QueryText: queryText,
|
QueryText: queryText,
|
||||||
Title: title,
|
Title: title,
|
||||||
Markdown: true,
|
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)
|
response, err := userinput.GetUserInput(ctx, request)
|
||||||
if err != nil || !response.Confirm {
|
if err != nil {
|
||||||
return err
|
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 {
|
if response.CheckboxStat {
|
||||||
meta := waveobj.MetaMapType{
|
meta := waveobj.MetaMapType{
|
||||||
wconfig.ConfigKey_ConnAskBeforeWshInstall: false,
|
wconfig.ConfigKey_ConnAskBeforeWshInstall: false,
|
||||||
@ -371,7 +389,7 @@ func (conn *SSHConn) Reconnect(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return conn.Connect(ctx)
|
return conn.Connect(ctx, &wshrpc.ConnKeywords{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *SSHConn) WaitForConnect(ctx context.Context) error {
|
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
|
// 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
|
var connectAllowed bool
|
||||||
conn.WithLock(func() {
|
conn.WithLock(func() {
|
||||||
if conn.Status == Status_Connecting || conn.Status == Status_Connected {
|
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())
|
return fmt.Errorf("cannot connect to %q when status is %q", conn.GetName(), conn.GetStatus())
|
||||||
}
|
}
|
||||||
conn.FireConnChangeEvent()
|
conn.FireConnChangeEvent()
|
||||||
err := conn.connectInternal(ctx)
|
err := conn.connectInternal(ctx, connFlags)
|
||||||
conn.WithLock(func() {
|
conn.WithLock(func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Status = Status_Error
|
conn.Status = Status_Error
|
||||||
conn.Error = err.Error()
|
conn.Error = err.Error()
|
||||||
conn.close_nolock()
|
conn.close_nolock()
|
||||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||||
Conn: map[string]int{"ssh:connecterror": 1},
|
Conn: map[string]int{"ssh:connecterror": 1},
|
||||||
}, "ssh-connconnect")
|
}, "ssh-connconnect")
|
||||||
} else {
|
} else {
|
||||||
@ -430,7 +448,7 @@ func (conn *SSHConn) Connect(ctx context.Context) error {
|
|||||||
if conn.ActiveConnNum == 0 {
|
if conn.ActiveConnNum == 0 {
|
||||||
conn.ActiveConnNum = int(activeConnCounter.Add(1))
|
conn.ActiveConnNum = int(activeConnCounter.Add(1))
|
||||||
}
|
}
|
||||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||||
Conn: map[string]int{"ssh:connect": 1},
|
Conn: map[string]int{"ssh:connect": 1},
|
||||||
}, "ssh-connconnect")
|
}, "ssh-connconnect")
|
||||||
}
|
}
|
||||||
@ -445,8 +463,8 @@ func (conn *SSHConn) WithLock(fn func()) {
|
|||||||
fn()
|
fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *SSHConn) connectInternal(ctx context.Context) error {
|
func (conn *SSHConn) connectInternal(ctx context.Context, connFlags *wshrpc.ConnKeywords) error {
|
||||||
client, _, err := remote.ConnectToClient(ctx, conn.Opts, nil, 0)
|
client, _, err := remote.ConnectToClient(ctx, conn.Opts, nil, 0, connFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error: failed to connect to client %s: %s\n", conn.GetName(), err)
|
log.Printf("error: failed to connect to client %s: %s\n", conn.GetName(), err)
|
||||||
return err
|
return err
|
||||||
@ -462,16 +480,41 @@ func (conn *SSHConn) connectInternal(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config := wconfig.ReadFullConfig()
|
config := wconfig.ReadFullConfig()
|
||||||
installErr := conn.CheckAndInstallWsh(ctx, clientDisplayName, &WshInstallOpts{NoUserPrompt: !config.Settings.ConnAskBeforeWshInstall})
|
enableWsh := config.Settings.ConnWshEnabled
|
||||||
if installErr != nil {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
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)
|
return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr)
|
||||||
|
} else {
|
||||||
|
conn.WshEnabled.Store(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conn.WshEnabled.Load() {
|
||||||
csErr := conn.StartConnServer()
|
csErr := conn.StartConnServer()
|
||||||
if csErr != nil {
|
if csErr != nil {
|
||||||
log.Printf("error: unable to start conn server for %s: %v\n", conn.GetName(), csErr)
|
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)
|
return fmt.Errorf("conncontroller %s start wsh connserver error: %v", conn.GetName(), csErr)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conn.WshEnabled.Store(false)
|
||||||
|
}
|
||||||
conn.HasWaiter.Store(true)
|
conn.HasWaiter.Store(true)
|
||||||
go conn.waitForDisconnect()
|
go conn.waitForDisconnect()
|
||||||
return nil
|
return nil
|
||||||
@ -504,16 +547,16 @@ func getConnInternal(opts *remote.SSHOpts) *SSHConn {
|
|||||||
defer globalLock.Unlock()
|
defer globalLock.Unlock()
|
||||||
rtn := clientControllerMap[*opts]
|
rtn := clientControllerMap[*opts]
|
||||||
if rtn == nil {
|
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
|
clientControllerMap[*opts] = rtn
|
||||||
}
|
}
|
||||||
return 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)
|
conn := getConnInternal(opts)
|
||||||
if conn.Client == nil && shouldConnect {
|
if conn.Client == nil && shouldConnect {
|
||||||
conn.Connect(ctx)
|
conn.Connect(ctx, connFlags)
|
||||||
}
|
}
|
||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
@ -527,7 +570,7 @@ func EnsureConnection(ctx context.Context, connName string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing connection name: %w", err)
|
return fmt.Errorf("error parsing connection name: %w", err)
|
||||||
}
|
}
|
||||||
conn := GetConn(ctx, connOpts, false)
|
conn := GetConn(ctx, connOpts, false, &wshrpc.ConnKeywords{})
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return fmt.Errorf("connection not found: %s", connName)
|
return fmt.Errorf("connection not found: %s", connName)
|
||||||
}
|
}
|
||||||
@ -538,7 +581,7 @@ func EnsureConnection(ctx context.Context, connName string) error {
|
|||||||
case Status_Connecting:
|
case Status_Connecting:
|
||||||
return conn.WaitForConnect(ctx)
|
return conn.WaitForConnect(ctx)
|
||||||
case Status_Init, Status_Disconnected:
|
case Status_Init, Status_Disconnected:
|
||||||
return conn.Connect(ctx)
|
return conn.Connect(ctx, &wshrpc.ConnKeywords{})
|
||||||
case Status_Error:
|
case Status_Error:
|
||||||
return fmt.Errorf("connection error: %s", connStatus.Error)
|
return fmt.Errorf("connection error: %s", connStatus.Error)
|
||||||
default:
|
default:
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -28,6 +27,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/pkg/userinput"
|
"github.com/wavetermdev/waveterm/pkg/userinput"
|
||||||
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/agent"
|
"golang.org/x/crypto/ssh/agent"
|
||||||
xknownhosts "golang.org/x/crypto/ssh/knownhosts"
|
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
|
// 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
|
// 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.
|
// 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
|
var identityFiles []string
|
||||||
existingKeys := make(map[string][]byte)
|
existingKeys := make(map[string][]byte)
|
||||||
|
|
||||||
// checking the file early prevents us from needing to send a
|
// checking the file early prevents us from needing to send a
|
||||||
// dummy signer if there's a problem with the signer
|
// 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)
|
filePath, err := wavebase.ExpandHomeDir(identityFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@ -151,7 +151,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
signer, err := ssh.NewSignerFromKey(unencryptedPrivateKey)
|
signer, err := ssh.NewSignerFromKey(unencryptedPrivateKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if sshKeywords.AddKeysToAgent && agentClient != nil {
|
if sshKeywords.SshAddKeysToAgent && agentClient != nil {
|
||||||
agentClient.Add(agent.AddedKey{
|
agentClient.Add(agent.AddedKey{
|
||||||
PrivateKey: unencryptedPrivateKey,
|
PrivateKey: unencryptedPrivateKey,
|
||||||
})
|
})
|
||||||
@ -165,7 +165,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// batch mode deactivates user input
|
// batch mode deactivates user input
|
||||||
if sshKeywords.BatchMode {
|
if sshKeywords.SshBatchMode {
|
||||||
// skip this key and try with the next
|
// skip this key and try with the next
|
||||||
return createDummySigner()
|
return createDummySigner()
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords,
|
|||||||
// skip this key and try with the next
|
// skip this key and try with the next
|
||||||
return createDummySigner()
|
return createDummySigner()
|
||||||
}
|
}
|
||||||
if sshKeywords.AddKeysToAgent && agentClient != nil {
|
if sshKeywords.SshAddKeysToAgent && agentClient != nil {
|
||||||
agentClient.Add(agent.AddedKey{
|
agentClient.Add(agent.AddedKey{
|
||||||
PrivateKey: unencryptedPrivateKey,
|
PrivateKey: unencryptedPrivateKey,
|
||||||
})
|
})
|
||||||
@ -333,7 +333,14 @@ func createUnknownKeyVerifier(knownHostsFile string, hostname string, remote str
|
|||||||
return func() (*userinput.UserInputResponse, error) {
|
return func() (*userinput.UserInputResponse, error) {
|
||||||
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
|
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
defer cancelFn()
|
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) {
|
return func() (*userinput.UserInputResponse, error) {
|
||||||
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
|
ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
defer cancelFn()
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func createHostKeyCallback(sshKeywords *SshKeywords) (ssh.HostKeyCallback, HostKeyAlgorithms, error) {
|
func createHostKeyCallback(sshKeywords *wshrpc.ConnKeywords) (ssh.HostKeyCallback, HostKeyAlgorithms, error) {
|
||||||
globalKnownHostsFiles := sshKeywords.GlobalKnownHostsFile
|
globalKnownHostsFiles := sshKeywords.SshGlobalKnownHostsFile
|
||||||
userKnownHostsFiles := sshKeywords.UserKnownHostsFile
|
userKnownHostsFiles := sshKeywords.SshUserKnownHostsFile
|
||||||
|
|
||||||
osUser, err := user.Current()
|
osUser, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -536,12 +550,12 @@ func createHostKeyCallback(sshKeywords *SshKeywords) (ssh.HostKeyCallback, HostK
|
|||||||
return waveHostKeyCallback, hostKeyAlgorithms, nil
|
return waveHostKeyCallback, hostKeyAlgorithms, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debugInfo *ConnectionDebugInfo) (*ssh.ClientConfig, error) {
|
func createClientConfig(connCtx context.Context, sshKeywords *wshrpc.ConnKeywords, debugInfo *ConnectionDebugInfo) (*ssh.ClientConfig, error) {
|
||||||
remoteName := sshKeywords.User + "@" + xknownhosts.Normalize(sshKeywords.HostName+":"+sshKeywords.Port)
|
remoteName := sshKeywords.SshUser + "@" + xknownhosts.Normalize(sshKeywords.SshHostName+":"+sshKeywords.SshPort)
|
||||||
|
|
||||||
var authSockSigners []ssh.Signer
|
var authSockSigners []ssh.Signer
|
||||||
var agentClient agent.ExtendedAgent
|
var agentClient agent.ExtendedAgent
|
||||||
conn, err := net.Dial("unix", sshKeywords.IdentityAgent)
|
conn, err := net.Dial("unix", sshKeywords.SshIdentityAgent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to open Identity Agent Socket: %v", err)
|
log.Printf("Failed to open Identity Agent Socket: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@ -555,20 +569,20 @@ func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debug
|
|||||||
|
|
||||||
// exclude gssapi-with-mic and hostbased until implemented
|
// exclude gssapi-with-mic and hostbased until implemented
|
||||||
authMethodMap := map[string]ssh.AuthMethod{
|
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),
|
"keyboard-interactive": ssh.RetryableAuthMethod(keyboardInteractive, 1),
|
||||||
"password": ssh.RetryableAuthMethod(passwordCallback, 1),
|
"password": ssh.RetryableAuthMethod(passwordCallback, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: batch mode turns off interactive input
|
// note: batch mode turns off interactive input
|
||||||
authMethodActiveMap := map[string]bool{
|
authMethodActiveMap := map[string]bool{
|
||||||
"publickey": sshKeywords.PubkeyAuthentication,
|
"publickey": sshKeywords.SshPubkeyAuthentication,
|
||||||
"keyboard-interactive": sshKeywords.KbdInteractiveAuthentication && !sshKeywords.BatchMode,
|
"keyboard-interactive": sshKeywords.SshKbdInteractiveAuthentication && !sshKeywords.SshBatchMode,
|
||||||
"password": sshKeywords.PasswordAuthentication && !sshKeywords.BatchMode,
|
"password": sshKeywords.SshPasswordAuthentication && !sshKeywords.SshBatchMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
var authMethods []ssh.AuthMethod
|
var authMethods []ssh.AuthMethod
|
||||||
for _, authMethodName := range sshKeywords.PreferredAuthentications {
|
for _, authMethodName := range sshKeywords.SshPreferredAuthentications {
|
||||||
authMethodActive, ok := authMethodActiveMap[authMethodName]
|
authMethodActive, ok := authMethodActiveMap[authMethodName]
|
||||||
if !ok || !authMethodActive {
|
if !ok || !authMethodActive {
|
||||||
continue
|
continue
|
||||||
@ -585,9 +599,9 @@ func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debug
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port
|
networkAddr := sshKeywords.SshHostName + ":" + sshKeywords.SshPort
|
||||||
return &ssh.ClientConfig{
|
return &ssh.ClientConfig{
|
||||||
User: sshKeywords.User,
|
User: sshKeywords.SshUser,
|
||||||
Auth: authMethods,
|
Auth: authMethods,
|
||||||
HostKeyCallback: hostKeyCallback,
|
HostKeyCallback: hostKeyCallback,
|
||||||
HostKeyAlgorithms: hostKeyAlgorithms(networkAddr),
|
HostKeyAlgorithms: hostKeyAlgorithms(networkAddr),
|
||||||
@ -616,7 +630,7 @@ func connectInternal(ctx context.Context, networkAddr string, clientConfig *ssh.
|
|||||||
return ssh.NewClient(c, chans, reqs), nil
|
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{
|
debugInfo := &ConnectionDebugInfo{
|
||||||
CurrentClient: currentClient,
|
CurrentClient: currentClient,
|
||||||
NextOpts: opts,
|
NextOpts: opts,
|
||||||
@ -631,12 +645,16 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
|||||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
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 {
|
if err != nil {
|
||||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, proxyName := range sshKeywords.ProxyJump {
|
for _, proxyName := range sshKeywords.SshProxyJump {
|
||||||
proxyOpts, err := ParseOpts(proxyName)
|
proxyOpts, err := ParseOpts(proxyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||||
@ -647,7 +665,8 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
|||||||
jumpNum += 1
|
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 {
|
if err != nil {
|
||||||
// do not add a context on a recursive call
|
// do not add a context on a recursive call
|
||||||
// (this can cause a recursive nested context that's arbitrarily deep)
|
// (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 {
|
if err != nil {
|
||||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
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)
|
client, err := connectInternal(connCtx, networkAddr, clientConfig, debugInfo.CurrentClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return client, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
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
|
return client, debugInfo.JumpNum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SshKeywords struct {
|
func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *wshrpc.ConnKeywords) (*wshrpc.ConnKeywords, error) {
|
||||||
User string
|
sshKeywords := &wshrpc.ConnKeywords{}
|
||||||
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(opts *SSHOpts, configKeywords *SshKeywords) (*SshKeywords, error) {
|
if userProvidedOpts.SshUser != "" {
|
||||||
sshKeywords := &SshKeywords{}
|
sshKeywords.SshUser = userProvidedOpts.SshUser
|
||||||
|
} else if configKeywords.SshUser != "" {
|
||||||
if opts.SSHUser != "" {
|
sshKeywords.SshUser = configKeywords.SshUser
|
||||||
sshKeywords.User = opts.SSHUser
|
|
||||||
} else if configKeywords.User != "" {
|
|
||||||
sshKeywords.User = configKeywords.User
|
|
||||||
} else {
|
} else {
|
||||||
user, err := user.Current()
|
user, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get user for ssh: %+v", err)
|
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 have to check the host value because of the weird way
|
||||||
// we store the pattern as the hostname for imported remotes
|
// we store the pattern as the hostname for imported remotes
|
||||||
if configKeywords.HostName != "" {
|
if configKeywords.SshHostName != "" {
|
||||||
sshKeywords.HostName = configKeywords.HostName
|
sshKeywords.SshHostName = configKeywords.SshHostName
|
||||||
} else {
|
} else {
|
||||||
sshKeywords.HostName = opts.SSHHost
|
sshKeywords.SshHostName = userProvidedOpts.SshHostName
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.SSHPort != 0 && opts.SSHPort != 22 {
|
if userProvidedOpts.SshPort != "0" && userProvidedOpts.SshPort != "22" {
|
||||||
sshKeywords.Port = strconv.Itoa(opts.SSHPort)
|
sshKeywords.SshPort = userProvidedOpts.SshPort
|
||||||
} else if configKeywords.Port != "" && configKeywords.Port != "22" {
|
} else if configKeywords.SshPort != "" && configKeywords.SshPort != "22" {
|
||||||
sshKeywords.Port = configKeywords.Port
|
sshKeywords.SshPort = configKeywords.SshPort
|
||||||
} else {
|
} 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
|
// these are not officially supported in the waveterm frontend but can be configured
|
||||||
// in ssh config files
|
// in ssh config files
|
||||||
sshKeywords.BatchMode = configKeywords.BatchMode
|
sshKeywords.SshBatchMode = configKeywords.SshBatchMode
|
||||||
sshKeywords.PubkeyAuthentication = configKeywords.PubkeyAuthentication
|
sshKeywords.SshPubkeyAuthentication = configKeywords.SshPubkeyAuthentication
|
||||||
sshKeywords.PasswordAuthentication = configKeywords.PasswordAuthentication
|
sshKeywords.SshPasswordAuthentication = configKeywords.SshPasswordAuthentication
|
||||||
sshKeywords.KbdInteractiveAuthentication = configKeywords.KbdInteractiveAuthentication
|
sshKeywords.SshKbdInteractiveAuthentication = configKeywords.SshKbdInteractiveAuthentication
|
||||||
sshKeywords.PreferredAuthentications = configKeywords.PreferredAuthentications
|
sshKeywords.SshPreferredAuthentications = configKeywords.SshPreferredAuthentications
|
||||||
sshKeywords.AddKeysToAgent = configKeywords.AddKeysToAgent
|
sshKeywords.SshAddKeysToAgent = configKeywords.SshAddKeysToAgent
|
||||||
sshKeywords.IdentityAgent = configKeywords.IdentityAgent
|
sshKeywords.SshIdentityAgent = configKeywords.SshIdentityAgent
|
||||||
sshKeywords.ProxyJump = configKeywords.ProxyJump
|
sshKeywords.SshProxyJump = configKeywords.SshProxyJump
|
||||||
sshKeywords.UserKnownHostsFile = configKeywords.UserKnownHostsFile
|
sshKeywords.SshUserKnownHostsFile = configKeywords.SshUserKnownHostsFile
|
||||||
sshKeywords.GlobalKnownHostsFile = configKeywords.GlobalKnownHostsFile
|
sshKeywords.SshGlobalKnownHostsFile = configKeywords.SshGlobalKnownHostsFile
|
||||||
|
|
||||||
return sshKeywords, nil
|
return sshKeywords, nil
|
||||||
}
|
}
|
||||||
@ -735,59 +737,60 @@ func combineSshKeywords(opts *SSHOpts, configKeywords *SshKeywords) (*SshKeyword
|
|||||||
// note that a `var == "yes"` will default to false
|
// note that a `var == "yes"` will default to false
|
||||||
// but `var != "no"` will default to true
|
// but `var != "no"` will default to true
|
||||||
// when given unexpected strings
|
// when given unexpected strings
|
||||||
func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
func findSshConfigKeywords(hostPattern string) (*wshrpc.ConnKeywords, error) {
|
||||||
WaveSshConfigUserSettings().ReloadConfigs()
|
WaveSshConfigUserSettings().ReloadConfigs()
|
||||||
sshKeywords := &SshKeywords{}
|
sshKeywords := &wshrpc.ConnKeywords{}
|
||||||
var err error
|
var err error
|
||||||
|
//config := wconfig.ReadFullConfig()
|
||||||
|
|
||||||
userRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "User")
|
userRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "User")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sshKeywords.User = trimquotes.TryTrimQuotes(userRaw)
|
sshKeywords.SshUser = trimquotes.TryTrimQuotes(userRaw)
|
||||||
|
|
||||||
hostNameRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "HostName")
|
hostNameRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "HostName")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sshKeywords.HostName = trimquotes.TryTrimQuotes(hostNameRaw)
|
sshKeywords.SshHostName = trimquotes.TryTrimQuotes(hostNameRaw)
|
||||||
|
|
||||||
portRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "Port")
|
portRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "Port")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sshKeywords.Port = trimquotes.TryTrimQuotes(portRaw)
|
sshKeywords.SshPort = trimquotes.TryTrimQuotes(portRaw)
|
||||||
|
|
||||||
identityFileRaw := WaveSshConfigUserSettings().GetAll(hostPattern, "IdentityFile")
|
identityFileRaw := WaveSshConfigUserSettings().GetAll(hostPattern, "IdentityFile")
|
||||||
for i := 0; i < len(identityFileRaw); i++ {
|
for i := 0; i < len(identityFileRaw); i++ {
|
||||||
identityFileRaw[i] = trimquotes.TryTrimQuotes(identityFileRaw[i])
|
identityFileRaw[i] = trimquotes.TryTrimQuotes(identityFileRaw[i])
|
||||||
}
|
}
|
||||||
sshKeywords.IdentityFile = identityFileRaw
|
sshKeywords.SshIdentityFile = identityFileRaw
|
||||||
|
|
||||||
batchModeRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "BatchMode")
|
batchModeRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "BatchMode")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// we currently do not support host-bound or unbound but will use yes when they are selected
|
||||||
pubkeyAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "PubkeyAuthentication")
|
pubkeyAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "PubkeyAuthentication")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
passwordAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "PasswordAuthentication")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
kbdInteractiveAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "KbdInteractiveAuthentication")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 parsed as a single string and must be separated
|
||||||
// these are case sensitive in openssh so they are here too
|
// these are case sensitive in openssh so they are here too
|
||||||
@ -795,12 +798,12 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sshKeywords.PreferredAuthentications = strings.Split(trimquotes.TryTrimQuotes(preferredAuthenticationsRaw), ",")
|
sshKeywords.SshPreferredAuthentications = strings.Split(trimquotes.TryTrimQuotes(preferredAuthenticationsRaw), ",")
|
||||||
addKeysToAgentRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "AddKeysToAgent")
|
addKeysToAgentRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "AddKeysToAgent")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
identityAgentRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "IdentityAgent")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -815,7 +818,7 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sshKeywords.IdentityAgent = agentPath
|
sshKeywords.SshIdentityAgent = agentPath
|
||||||
} else {
|
} else {
|
||||||
log.Printf("unable to find SSH_AUTH_SOCK: %v\n", err)
|
log.Printf("unable to find SSH_AUTH_SOCK: %v\n", err)
|
||||||
}
|
}
|
||||||
@ -824,7 +827,7 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sshKeywords.IdentityAgent = agentPath
|
sshKeywords.SshIdentityAgent = agentPath
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyJumpRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "ProxyJump")
|
proxyJumpRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "ProxyJump")
|
||||||
@ -837,12 +840,12 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) {
|
|||||||
if proxyJumpName == "" || strings.ToLower(proxyJumpName) == "none" {
|
if proxyJumpName == "" || strings.ToLower(proxyJumpName) == "none" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sshKeywords.ProxyJump = append(sshKeywords.ProxyJump, proxyJumpName)
|
sshKeywords.SshProxyJump = append(sshKeywords.SshProxyJump, proxyJumpName)
|
||||||
}
|
}
|
||||||
rawUserKnownHostsFile, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "UserKnownHostsFile")
|
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")
|
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
|
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) {
|
func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
|
||||||
client := conn.GetClient()
|
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
|
shellPath := cmdOpts.ShellPath
|
||||||
if shellPath == "" {
|
if shellPath == "" {
|
||||||
remoteShellPath, err := remote.DetectShell(client)
|
remoteShellPath, err := remote.DetectShell(client)
|
||||||
|
@ -14,41 +14,12 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/pkg/util/dbutil"
|
"github.com/wavetermdev/waveterm/pkg/util/dbutil"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wstore"
|
"github.com/wavetermdev/waveterm/pkg/wstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxTzNameLen = 50
|
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 {
|
type ActivityType struct {
|
||||||
Day string `json:"day"`
|
Day string `json:"day"`
|
||||||
Uploaded bool `json:"-"`
|
Uploaded bool `json:"-"`
|
||||||
@ -76,7 +47,7 @@ type TelemetryData struct {
|
|||||||
NumShutdown int `json:"numshutdown,omitempty"`
|
NumShutdown int `json:"numshutdown,omitempty"`
|
||||||
NumPanics int `json:"numpanics,omitempty"`
|
NumPanics int `json:"numpanics,omitempty"`
|
||||||
SetTabTheme int `json:"settabtheme,omitempty"`
|
SetTabTheme int `json:"settabtheme,omitempty"`
|
||||||
Displays []ActivityDisplayType `json:"displays,omitempty"`
|
Displays []wshrpc.ActivityDisplayType `json:"displays,omitempty"`
|
||||||
Renderers map[string]int `json:"renderers,omitempty"`
|
Renderers map[string]int `json:"renderers,omitempty"`
|
||||||
Blocks map[string]int `json:"blocks,omitempty"`
|
Blocks map[string]int `json:"blocks,omitempty"`
|
||||||
WshCmds map[string]int `json:"wshcmds,omitempty"`
|
WshCmds map[string]int `json:"wshcmds,omitempty"`
|
||||||
@ -107,7 +78,7 @@ func AutoUpdateChannel() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wraps UpdateCurrentActivity, spawns goroutine, and logs errors
|
// Wraps UpdateCurrentActivity, spawns goroutine, and logs errors
|
||||||
func GoUpdateActivityWrap(update ActivityUpdate, debugStr string) {
|
func GoUpdateActivityWrap(update wshrpc.ActivityUpdate, debugStr string) {
|
||||||
go func() {
|
go func() {
|
||||||
defer panichandler.PanicHandlerNoTelemetry("GoUpdateActivityWrap")
|
defer panichandler.PanicHandlerNoTelemetry("GoUpdateActivityWrap")
|
||||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
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()
|
now := time.Now()
|
||||||
dayStr := daystr.GetCurDayStr()
|
dayStr := daystr.GetCurDayStr()
|
||||||
txErr := wstore.WithTx(ctx, func(tx *wstore.TxWrap) error {
|
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 errorRType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
var anyRType = reflect.TypeOf((*interface{})(nil)).Elem()
|
var anyRType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||||
var metaRType = reflect.TypeOf((*waveobj.MetaMapType)(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 uiContextRType = reflect.TypeOf((*waveobj.UIContext)(nil)).Elem()
|
||||||
var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
|
var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
|
||||||
var updatesRtnRType = reflect.TypeOf(waveobj.UpdatesRtnType{})
|
var updatesRtnRType = reflect.TypeOf(waveobj.UpdatesRtnType{})
|
||||||
|
@ -25,6 +25,8 @@ type UserInputRequest struct {
|
|||||||
TimeoutMs int `json:"timeoutms"`
|
TimeoutMs int `json:"timeoutms"`
|
||||||
CheckBoxMsg string `json:"checkboxmsg"`
|
CheckBoxMsg string `json:"checkboxmsg"`
|
||||||
PublicText bool `json:"publictext"`
|
PublicText bool `json:"publictext"`
|
||||||
|
OkLabel string `json:"oklabel,omitempty"`
|
||||||
|
CancelLabel string `json:"cancellabel,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserInputResponse struct {
|
type UserInputResponse struct {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"autoupdate:installonquit": true,
|
"autoupdate:installonquit": true,
|
||||||
"autoupdate:intervalms": 3600000,
|
"autoupdate:intervalms": 3600000,
|
||||||
"conn:askbeforewshinstall": true,
|
"conn:askbeforewshinstall": true,
|
||||||
|
"conn:wshenabled": true,
|
||||||
"editor:minimapenabled": true,
|
"editor:minimapenabled": true,
|
||||||
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
|
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
|
||||||
"web:defaultsearch": "https://www.google.com/search?q={query}",
|
"web:defaultsearch": "https://www.google.com/search?q={query}",
|
||||||
|
@ -71,5 +71,6 @@ const (
|
|||||||
|
|
||||||
ConfigKey_ConnClear = "conn:*"
|
ConfigKey_ConnClear = "conn:*"
|
||||||
ConfigKey_ConnAskBeforeWshInstall = "conn:askbeforewshinstall"
|
ConfigKey_ConnAskBeforeWshInstall = "conn:askbeforewshinstall"
|
||||||
|
ConfigKey_ConnWshEnabled = "conn:wshenabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,9 +19,11 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
||||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wconfig/defaultconfig"
|
"github.com/wavetermdev/waveterm/pkg/wconfig/defaultconfig"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SettingsFile = "settings.json"
|
const SettingsFile = "settings.json"
|
||||||
|
const ConnectionsFile = "connections.json"
|
||||||
|
|
||||||
const AnySchema = `
|
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 {
|
type SettingsType struct {
|
||||||
AiClear bool `json:"ai:*,omitempty"`
|
AiClear bool `json:"ai:*,omitempty"`
|
||||||
AiPreset string `json:"ai:preset,omitempty"`
|
AiPreset string `json:"ai:preset,omitempty"`
|
||||||
@ -115,6 +98,7 @@ type SettingsType struct {
|
|||||||
|
|
||||||
ConnClear bool `json:"conn:*,omitempty"`
|
ConnClear bool `json:"conn:*,omitempty"`
|
||||||
ConnAskBeforeWshInstall bool `json:"conn:askbeforewshinstall,omitempty"`
|
ConnAskBeforeWshInstall bool `json:"conn:askbeforewshinstall,omitempty"`
|
||||||
|
ConnWshEnabled bool `json:"conn:wshenabled,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigError struct {
|
type ConfigError struct {
|
||||||
@ -125,9 +109,11 @@ type ConfigError struct {
|
|||||||
type FullConfigType struct {
|
type FullConfigType struct {
|
||||||
Settings SettingsType `json:"settings" merge:"meta"`
|
Settings SettingsType `json:"settings" merge:"meta"`
|
||||||
MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"`
|
MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"`
|
||||||
|
DefaultWidgets map[string]WidgetConfigType `json:"defaultwidgets"`
|
||||||
Widgets map[string]WidgetConfigType `json:"widgets"`
|
Widgets map[string]WidgetConfigType `json:"widgets"`
|
||||||
Presets map[string]waveobj.MetaMapType `json:"presets"`
|
Presets map[string]waveobj.MetaMapType `json:"presets"`
|
||||||
TermThemes map[string]TermThemeType `json:"termthemes"`
|
TermThemes map[string]TermThemeType `json:"termthemes"`
|
||||||
|
Connections map[string]wshrpc.ConnKeywords `json:"connections"`
|
||||||
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,12 +387,12 @@ func reindentJson(barr []byte, indentStr string) []byte {
|
|||||||
if barr[0] != '{' && barr[0] != '[' {
|
if barr[0] != '{' && barr[0] != '[' {
|
||||||
return barr
|
return barr
|
||||||
}
|
}
|
||||||
if bytes.Contains(barr, []byte("\n")) {
|
if !bytes.Contains(barr, []byte("\n")) {
|
||||||
return barr
|
return barr
|
||||||
}
|
}
|
||||||
outputLines := bytes.Split(barr, []byte("\n"))
|
outputLines := bytes.Split(barr, []byte("\n"))
|
||||||
for i, line := range outputLines {
|
for i, line := range outputLines {
|
||||||
if i == 0 || i == len(outputLines)-1 {
|
if i == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
outputLines[i] = append([]byte(indentStr), line...)
|
outputLines[i] = append([]byte(indentStr), line...)
|
||||||
@ -509,6 +495,25 @@ func SetBaseConfigValue(toMerge waveobj.MetaMapType) error {
|
|||||||
return WriteWaveHomeConfigFile(SettingsFile, m)
|
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 {
|
type WidgetConfigType struct {
|
||||||
DisplayOrder float64 `json:"display:order,omitempty"`
|
DisplayOrder float64 `json:"display:order,omitempty"`
|
||||||
Icon string `json:"icon,omitempty"`
|
Icon string `json:"icon,omitempty"`
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
||||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wstore"
|
"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)
|
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
|
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)
|
tctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
defer cancelFn()
|
defer cancelFn()
|
||||||
telemetry.UpdateActivity(tctx, telemetry.ActivityUpdate{
|
telemetry.UpdateActivity(tctx, wshrpc.ActivityUpdate{
|
||||||
Renderers: map[string]int{blockView: 1},
|
Renderers: map[string]int{blockView: 1},
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
@ -9,14 +9,12 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
|
||||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||||
"github.com/wavetermdev/waveterm/pkg/vdom"
|
"github.com/wavetermdev/waveterm/pkg/vdom"
|
||||||
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// command "activity", wshserver.ActivityCommand
|
// 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)
|
_, err := sendRpcRequestCallHelper[any](w, "activity", data, opts)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -40,7 +38,7 @@ func BlockInfoCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (*ws
|
|||||||
}
|
}
|
||||||
|
|
||||||
// command "connconnect", wshserver.ConnConnectCommand
|
// 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)
|
_, err := sendRpcRequestCallHelper[any](w, "connconnect", data, opts)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -290,7 +288,7 @@ func RouteUnannounceCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// command "setconfig", wshserver.SetConfigCommand
|
// 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)
|
_, err := sendRpcRequestCallHelper[any](w, "setconfig", data, opts)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
package wshrpc
|
package wshrpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/wavetermdev/waveterm/pkg/filestore"
|
"github.com/wavetermdev/waveterm/pkg/filestore"
|
||||||
"github.com/wavetermdev/waveterm/pkg/ijson"
|
"github.com/wavetermdev/waveterm/pkg/ijson"
|
||||||
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
|
||||||
"github.com/wavetermdev/waveterm/pkg/vdom"
|
"github.com/wavetermdev/waveterm/pkg/vdom"
|
||||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
|
||||||
"github.com/wavetermdev/waveterm/pkg/wps"
|
"github.com/wavetermdev/waveterm/pkg/wps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -133,11 +133,11 @@ type WshRpcInterface interface {
|
|||||||
StreamWaveAiCommand(ctx context.Context, request OpenAiStreamRequest) chan RespOrErrorUnion[OpenAIPacketType]
|
StreamWaveAiCommand(ctx context.Context, request OpenAiStreamRequest) chan RespOrErrorUnion[OpenAIPacketType]
|
||||||
StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData]
|
StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData]
|
||||||
TestCommand(ctx context.Context, data string) error
|
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)
|
BlockInfoCommand(ctx context.Context, blockId string) (*BlockInfoData, error)
|
||||||
WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)
|
WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)
|
||||||
WshActivityCommand(ct context.Context, data map[string]int) 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)
|
GetVarCommand(ctx context.Context, data CommandVarData) (*CommandVarResponseData, error)
|
||||||
SetVarCommand(ctx context.Context, data CommandVarData) error
|
SetVarCommand(ctx context.Context, data CommandVarData) error
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ type WshRpcInterface interface {
|
|||||||
WslStatusCommand(ctx context.Context) ([]ConnStatus, error)
|
WslStatusCommand(ctx context.Context) ([]ConnStatus, error)
|
||||||
ConnEnsureCommand(ctx context.Context, connName string) error
|
ConnEnsureCommand(ctx context.Context, connName string) error
|
||||||
ConnReinstallWshCommand(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
|
ConnDisconnectCommand(ctx context.Context, connName string) error
|
||||||
ConnListCommand(ctx context.Context) ([]string, error)
|
ConnListCommand(ctx context.Context) ([]string, error)
|
||||||
WslListCommand(ctx context.Context) ([]string, error)
|
WslListCommand(ctx context.Context) ([]string, error)
|
||||||
@ -440,6 +440,31 @@ type CommandRemoteWriteFileData struct {
|
|||||||
CreateMode os.FileMode `json:"createmode,omitempty"`
|
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 (
|
const (
|
||||||
TimeSeries_Cpu = "cpu"
|
TimeSeries_Cpu = "cpu"
|
||||||
)
|
)
|
||||||
@ -449,8 +474,28 @@ type TimeSeriesData struct {
|
|||||||
Values map[string]float64 `json:"values"`
|
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 {
|
type ConnStatus struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
WshEnabled bool `json:"wshenabled"`
|
||||||
Connection string `json:"connection"`
|
Connection string `json:"connection"`
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
HasConnected bool `json:"hasconnected"` // true if it has *ever* connected successfully
|
HasConnected bool `json:"hasconnected"` // true if it has *ever* connected successfully
|
||||||
@ -522,3 +567,33 @@ type CommandVarResponseData struct {
|
|||||||
Val string `json:"val"`
|
Val string `json:"val"`
|
||||||
Exists bool `json:"exists"`
|
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
|
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)
|
log.Printf("SETCONFIG: %v\n", data)
|
||||||
return wconfig.SetBaseConfigValue(data.MetaMapType)
|
return wconfig.SetBaseConfigValue(data.MetaMapType)
|
||||||
}
|
}
|
||||||
@ -623,14 +623,15 @@ func (ws *WshServer) ConnDisconnectCommand(ctx context.Context, connName string)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing connection name: %w", err)
|
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 {
|
if conn == nil {
|
||||||
return fmt.Errorf("connection not found: %s", connName)
|
return fmt.Errorf("connection not found: %s", connName)
|
||||||
}
|
}
|
||||||
return conn.Close()
|
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://") {
|
if strings.HasPrefix(connName, "wsl://") {
|
||||||
distroName := strings.TrimPrefix(connName, "wsl://")
|
distroName := strings.TrimPrefix(connName, "wsl://")
|
||||||
conn := wsl.GetWslConn(ctx, distroName, false)
|
conn := wsl.GetWslConn(ctx, distroName, false)
|
||||||
@ -643,11 +644,11 @@ func (ws *WshServer) ConnConnectCommand(ctx context.Context, connName string) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing connection name: %w", err)
|
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 {
|
if conn == nil {
|
||||||
return fmt.Errorf("connection not found: %s", connName)
|
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 {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing connection name: %w", err)
|
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 {
|
if conn == nil {
|
||||||
return fmt.Errorf("connection not found: %s", connName)
|
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)
|
delete(data, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activityUpdate := telemetry.ActivityUpdate{
|
activityUpdate := wshrpc.ActivityUpdate{
|
||||||
WshCmds: data,
|
WshCmds: data,
|
||||||
}
|
}
|
||||||
telemetry.GoUpdateActivityWrap(activityUpdate, "wsh-activity")
|
telemetry.GoUpdateActivityWrap(activityUpdate, "wsh-activity")
|
||||||
return nil
|
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")
|
telemetry.GoUpdateActivityWrap(activity, "wshrpc-activity")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -394,7 +394,7 @@ func (conn *WslConn) Connect(ctx context.Context) error {
|
|||||||
conn.Status = Status_Error
|
conn.Status = Status_Error
|
||||||
conn.Error = err.Error()
|
conn.Error = err.Error()
|
||||||
conn.close_nolock()
|
conn.close_nolock()
|
||||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||||
Conn: map[string]int{"wsl:connecterror": 1},
|
Conn: map[string]int{"wsl:connecterror": 1},
|
||||||
}, "wsl-connconnect")
|
}, "wsl-connconnect")
|
||||||
} else {
|
} else {
|
||||||
@ -403,7 +403,7 @@ func (conn *WslConn) Connect(ctx context.Context) error {
|
|||||||
if conn.ActiveConnNum == 0 {
|
if conn.ActiveConnNum == 0 {
|
||||||
conn.ActiveConnNum = int(activeConnCounter.Add(1))
|
conn.ActiveConnNum = int(activeConnCounter.Add(1))
|
||||||
}
|
}
|
||||||
telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{
|
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{
|
||||||
Conn: map[string]int{"wsl:connect": 1},
|
Conn: map[string]int{"wsl:connect": 1},
|
||||||
}, "wsl-connconnect")
|
}, "wsl-connconnect")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user