From f122808d867039b7982108a022eb68870b3709f1 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 4 May 2021 12:27:50 +0100 Subject: [PATCH] fs: add names to each config parameter so we can override them #3455 --- backend/drive/drive.go | 6 +- backend/googlephotos/googlephotos.go | 2 +- backend/jottacloud/jottacloud.go | 18 +++--- backend/onedrive/onedrive.go | 18 +++--- backend/seafile/seafile.go | 8 +-- backend/sugarsync/sugarsync.go | 6 +- backend/zoho/zoho.go | 4 +- fs/backend_config.go | 90 ++++++++++++++++++---------- fs/config/ui.go | 2 +- lib/oauthutil/oauthutil.go | 8 +-- 10 files changed, 96 insertions(+), 66 deletions(-) diff --git a/backend/drive/drive.go b/backend/drive/drive.go index 7c2d003ca..240e6a189 100755 --- a/backend/drive/drive.go +++ b/backend/drive/drive.go @@ -208,9 +208,9 @@ func init() { return fs.ConfigGoto("teamdrive") case "teamdrive": if opt.TeamDriveID == "" { - return fs.ConfigConfirm("teamdrive_ok", false, "Configure this as a Shared Drive (Team Drive)?\n") + return fs.ConfigConfirm("teamdrive_ok", false, "config_change_team_drive", "Configure this as a Shared Drive (Team Drive)?\n") } - return fs.ConfigConfirm("teamdrive_ok", false, fmt.Sprintf("Change current Shared Drive (Team Drive) ID %q?\n", opt.TeamDriveID)) + return fs.ConfigConfirm("teamdrive_ok", false, "config_change_team_drive", fmt.Sprintf("Change current Shared Drive (Team Drive) ID %q?\n", opt.TeamDriveID)) case "teamdrive_ok": if config.Result == "false" { m.Set("team_drive", "") @@ -227,7 +227,7 @@ func init() { if len(teamDrives) == 0 { return fs.ConfigError("", "No Shared Drives found in your account") } - return fs.ConfigChoose("teamdrive_final", "Shared Drive", len(teamDrives), func(i int) (string, string) { + return fs.ConfigChoose("teamdrive_final", "config_team_drive", "Shared Drive", len(teamDrives), func(i int) (string, string) { teamDrive := teamDrives[i] return teamDrive.Id, teamDrive.Name }) diff --git a/backend/googlephotos/googlephotos.go b/backend/googlephotos/googlephotos.go index e82295167..7019eee29 100644 --- a/backend/googlephotos/googlephotos.go +++ b/backend/googlephotos/googlephotos.go @@ -98,7 +98,7 @@ func init() { }) case "warning": // Warn the user as required by google photos integration - return fs.ConfigConfirm("warning_done", true, `Warning + return fs.ConfigConfirm("warning_done", true, "config_warning", `Warning IMPORTANT: All media items uploaded to Google Photos with rclone are stored in full resolution at original quality. These uploads diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go index 3ac1afde1..a1c4d76fd 100644 --- a/backend/jottacloud/jottacloud.go +++ b/backend/jottacloud/jottacloud.go @@ -126,7 +126,7 @@ func init() { func Config(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) { switch config.State { case "": - return fs.ConfigChooseFixed("auth_type_done", `Authentication type`, []fs.OptionExample{{ + return fs.ConfigChooseFixed("auth_type_done", "config_type", `Authentication type`, []fs.OptionExample{{ Value: "standard", Help: "Standard authentication - use this if you're a normal Jottacloud user.", }, { @@ -141,7 +141,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf return fs.ConfigGoto(config.Result) case "standard": // configure a jottacloud backend using the modern JottaCli token based authentication m.Set("configVersion", fmt.Sprint(configVersion)) - return fs.ConfigInput("standard_token", "Personal login token.\n\nGenerate here: https://www.jottacloud.com/web/secure") + return fs.ConfigInput("standard_token", "config_login_token", "Personal login token.\n\nGenerate here: https://www.jottacloud.com/web/secure") case "standard_token": loginToken := config.Result m.Set(configClientID, "jottacli") @@ -159,7 +159,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf return fs.ConfigGoto("choose_device") case "legacy": // configure a jottacloud backend using legacy authentication m.Set("configVersion", fmt.Sprint(v1configVersion)) - return fs.ConfigConfirm("legacy_api", false, `Do you want to create a machine specific API key? + return fs.ConfigConfirm("legacy_api", false, "config_machine_specific", `Do you want to create a machine specific API key? Rclone has it's own Jottacloud API KEY which works fine as long as one only uses rclone on a single machine. When you want to use rclone with @@ -177,10 +177,10 @@ machines.`) m.Set(configClientSecret, obscure.MustObscure(deviceRegistration.ClientSecret)) fs.Debugf(nil, "Got clientID %q and clientSecret %q", deviceRegistration.ClientID, deviceRegistration.ClientSecret) } - return fs.ConfigInput("legacy_user", "Username") + return fs.ConfigInput("legacy_user", "config_user", "Username") case "legacy_username": m.Set(configUsername, config.Result) - return fs.ConfigPassword("legacy_password", "Jottacloud password\n\n(this is only required during setup and will not be stored).") + return fs.ConfigPassword("legacy_password", "config_password", "Jottacloud password\n\n(this is only required during setup and will not be stored).") case "legacy_password": m.Set("password", config.Result) m.Set("auth_code", "") @@ -213,7 +213,7 @@ machines.`) token, err := doAuthV1(ctx, srv, username, password, authCode) if err == errAuthCodeRequired { - return fs.ConfigInput("legacy_auth_code", "Verification Code\nThis account uses 2 factor authentication you will receive a verification code via SMS.") + return fs.ConfigInput("legacy_auth_code", "config_auth_code", "Verification Code\nThis account uses 2 factor authentication you will receive a verification code via SMS.") } m.Set("password", "") m.Set("auth_code", "") @@ -241,7 +241,7 @@ machines.`) }, }) case "choose_device": - return fs.ConfigConfirm("choose_device_query", false, "Use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?") + return fs.ConfigConfirm("choose_device_query", false, "config_non_standard", "Use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?") case "choose_device_query": if config.Result != "true" { m.Set(configDevice, "") @@ -265,7 +265,7 @@ machines.`) if err != nil { return nil, err } - return fs.ConfigChoose("choose_device_result", `Please select the device to use. Normally this will be Jotta`, len(acc.Devices), func(i int) (string, string) { + return fs.ConfigChoose("choose_device_result", "config_device", `Please select the device to use. Normally this will be Jotta`, len(acc.Devices), func(i int) (string, string) { return acc.Devices[i].Name, "" }) case "choose_device_result": @@ -283,7 +283,7 @@ machines.`) if err != nil { return nil, err } - return fs.ConfigChoose("choose_device_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) { + return fs.ConfigChoose("choose_device_mountpoint", "config_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) { return dev.MountPoints[i].Name, "" }) case "choose_device_mountpoint": diff --git a/backend/onedrive/onedrive.go b/backend/onedrive/onedrive.go index de92794be..31a707239 100755 --- a/backend/onedrive/onedrive.go +++ b/backend/onedrive/onedrive.go @@ -363,7 +363,7 @@ func chooseDrive(ctx context.Context, name string, m configmap.Mapper, srv *rest if len(drives.Drives) == 0 { return fs.ConfigError("choose_type", "No drives found") } - return fs.ConfigChoose("driveid_final", "Select drive you want to use", len(drives.Drives), func(i int) (string, string) { + return fs.ConfigChoose("driveid_final", "config_driveid", "Select drive you want to use", len(drives.Drives), func(i int) (string, string) { drive := drives.Drives[i] return drive.DriveID, fmt.Sprintf("%s (%s)", drive.DriveName, drive.DriveType) }) @@ -388,7 +388,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf OAuth2Config: oauthConfig, }) case "choose_type": - return fs.ConfigChooseFixed("choose_type_done", "Type of connection", []fs.OptionExample{{ + return fs.ConfigChooseFixed("choose_type_done", "config_type", "Type of connection", []fs.OptionExample{{ Value: "onedrive", Help: "OneDrive Personal or Business", }, { @@ -430,19 +430,19 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf }, }) case "driveid": - return fs.ConfigInput("driveid_end", "Drive ID") + return fs.ConfigInput("driveid_end", "config_driveid_fixed", "Drive ID") case "driveid_end": return chooseDrive(ctx, name, m, srv, chooseDriveOpt{ finalDriveID: config.Result, }) case "siteid": - return fs.ConfigInput("siteid_end", "Site ID") + return fs.ConfigInput("siteid_end", "config_siteid", "Site ID") case "siteid_end": return chooseDrive(ctx, name, m, srv, chooseDriveOpt{ siteID: config.Result, }) case "url": - return fs.ConfigInput("url_end", `Site URL + return fs.ConfigInput("url_end", "config_site_url", `Site URL Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite" `) @@ -459,13 +459,13 @@ Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite" relativePath: "/sites/" + siteURL, }) case "path": - return fs.ConfigInput("path_end", `Server-relative URL`) + return fs.ConfigInput("path_end", "config_sharepoint_url", `Server-relative URL`) case "path_end": return chooseDrive(ctx, name, m, srv, chooseDriveOpt{ relativePath: config.Result, }) case "search": - return fs.ConfigInput("search_end", `Search term`) + return fs.ConfigInput("search_end", "config_search_term", `Search term`) case "search_end": searchTerm := config.Result opts := rest.Opts{ @@ -483,7 +483,7 @@ Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite" if len(sites.Sites) == 0 { return fs.ConfigError("choose_type", fmt.Sprintf("search for %q returned no results", searchTerm)) } - return fs.ConfigChoose("search_sites", `Select the Site you want to use`, len(sites.Sites), func(i int) (string, string) { + return fs.ConfigChoose("search_sites", "config_site", `Select the Site you want to use`, len(sites.Sites), func(i int) (string, string) { site := sites.Sites[i] return site.SiteID, fmt.Sprintf("%s (%s)", site.SiteName, site.SiteURL) }) @@ -508,7 +508,7 @@ Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite" m.Set(configDriveID, finalDriveID) m.Set(configDriveType, rootItem.ParentReference.DriveType) - return fs.ConfigConfirm("driveid_final_end", true, fmt.Sprintf("Drive OK?\n\nFound drive %q of type %q\nURL: %s\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL)) + return fs.ConfigConfirm("driveid_final_end", true, "config_drive_ok", fmt.Sprintf("Drive OK?\n\nFound drive %q of type %q\nURL: %s\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL)) case "driveid_final_end": if config.Result == "true" { return nil, nil diff --git a/backend/seafile/seafile.go b/backend/seafile/seafile.go index 810fa0494..e6f744ba3 100644 --- a/backend/seafile/seafile.go +++ b/backend/seafile/seafile.go @@ -327,7 +327,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf case "": // Just make sure we do have a password if password == "" { - return fs.ConfigPassword("", "Two-factor authentication: please enter your password (it won't be saved in the configuration)") + return fs.ConfigPassword("", "config_password", "Two-factor authentication: please enter your password (it won't be saved in the configuration)") } return fs.ConfigGoto("password") case "password": @@ -338,7 +338,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf m.Set(configPassword, obscure.MustObscure(config.Result)) return fs.ConfigGoto("2fa") case "2fa": - return fs.ConfigInput("2fa_do", "Two-factor authentication: please enter your 2FA code") + return fs.ConfigInput("2fa_do", "config_2fa", "Two-factor authentication: please enter your 2FA code") case "2fa_do": code := config.Result if code == "" { @@ -358,10 +358,10 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf token, err := getAuthorizationToken(ctx, srv, username, password, code) if err != nil { - return fs.ConfigConfirm("2fa_error", true, fmt.Sprintf("Authentication failed: %v\n\nTry Again?", err)) + return fs.ConfigConfirm("2fa_error", true, "config_retry", fmt.Sprintf("Authentication failed: %v\n\nTry Again?", err)) } if token == "" { - return fs.ConfigConfirm("2fa_error", true, "Authentication failed - no token returned.\n\nTry Again?") + return fs.ConfigConfirm("2fa_error", true, "config_retry", "Authentication failed - no token returned.\n\nTry Again?") } // Let's save the token into the configuration m.Set(configAuthToken, token) diff --git a/backend/sugarsync/sugarsync.go b/backend/sugarsync/sugarsync.go index 36812a7bc..e195c4e81 100644 --- a/backend/sugarsync/sugarsync.go +++ b/backend/sugarsync/sugarsync.go @@ -87,17 +87,17 @@ func init() { if opt.RefreshToken == "" { return fs.ConfigGoto("username") } - return fs.ConfigConfirm("refresh", true, "Already have a token - refresh?") + return fs.ConfigConfirm("refresh", true, "config_refresh", "Already have a token - refresh?") case "refresh": if config.Result == "false" { return nil, nil } return fs.ConfigGoto("username") case "username": - return fs.ConfigInput("password", "username (email address)") + return fs.ConfigInput("password", "config_username", "username (email address)") case "password": m.Set("username", config.Result) - return fs.ConfigPassword("auth", "Your Sugarsync password.\n\nOnly required during setup and will not be stored.") + return fs.ConfigPassword("auth", "config_password", "Your Sugarsync password.\n\nOnly required during setup and will not be stored.") case "auth": username, _ := m.Get("username") m.Set("username", "") diff --git a/backend/zoho/zoho.go b/backend/zoho/zoho.go index 322d2c07b..acf1dd4af 100644 --- a/backend/zoho/zoho.go +++ b/backend/zoho/zoho.go @@ -131,7 +131,7 @@ func init() { if err != nil { return nil, err } - return fs.ConfigChoose("workspace", "Team Drive ID", len(teams), func(i int) (string, string) { + return fs.ConfigChoose("workspace", "config_team_drive_id", "Team Drive ID", len(teams), func(i int) (string, string) { team := teams[i] return team.ID, team.Attributes.Name }) @@ -145,7 +145,7 @@ func init() { if err != nil { return nil, err } - return fs.ConfigChoose("workspace_end", "Workspace ID", len(workspaces), func(i int) (string, string) { + return fs.ConfigChoose("workspace_end", "config_workspace", "Workspace ID", len(workspaces), func(i int) (string, string) { workspace := workspaces[i] return workspace.ID, workspace.Attributes.Name }) diff --git a/fs/backend_config.go b/fs/backend_config.go index e53406510..44288c03c 100644 --- a/fs/backend_config.go +++ b/fs/backend_config.go @@ -25,7 +25,9 @@ var ConfigOAuth func(ctx context.Context, name string, m configmap.Mapper, ri *R // ConfigIn is passed to the Config function for an Fs // -// The interactive config system for backends is state based. This is so that different frontends to the config can be attached, eg over the API or web page. +// The interactive config system for backends is state based. This is +// so that different frontends to the config can be attached, eg over +// the API or web page. // // Each call to the config system supplies ConfigIn which tells the // system what to do. Each will return a ConfigOut which gives a @@ -47,6 +49,10 @@ var ConfigOAuth func(ctx context.Context, name string, m configmap.Mapper, ri *R // // The utilities here are convenience methods for different kinds of // questions and responses. +// +// Where the questions ask for a name then this should start with +// "config_" to show it is an ephemeral config input rather than the +// actual value stored in the config file. type ConfigIn struct { State string // State to run Result string // Result from previous Option @@ -66,33 +72,42 @@ type ConfigOut struct { Result string // if Option/OAuth not set then this is passed to the next state } -// ConfigInput asks the user for a string +// ConfigInputOptional asks the user for a string which may be empty // // state should be the next state required +// name is the config name for this item // help should be the help shown to the user -func ConfigInput(state string, help string) (*ConfigOut, error) { +func ConfigInputOptional(state string, name string, help string) (*ConfigOut, error) { return &ConfigOut{ State: state, Option: &Option{ + Name: name, Help: help, Default: "", }, }, nil } +// ConfigInput asks the user for a non-empty string +// +// state should be the next state required +// name is the config name for this item +// help should be the help shown to the user +func ConfigInput(state string, name string, help string) (*ConfigOut, error) { + out, _ := ConfigInputOptional(state, name, help) + out.Option.Required = true + return out, nil +} + // ConfigPassword asks the user for a password // // state should be the next state required +// name is the config name for this item // help should be the help shown to the user -func ConfigPassword(state string, help string) (*ConfigOut, error) { - return &ConfigOut{ - State: state, - Option: &Option{ - Help: help, - Default: "", - IsPassword: true, - }, - }, nil +func ConfigPassword(state string, name string, help string) (*ConfigOut, error) { + out, _ := ConfigInputOptional(state, name, help) + out.Option.IsPassword = true + return out, nil } // ConfigGoto goes to the next state with empty Result @@ -130,11 +145,13 @@ func ConfigError(state string, Error string) (*ConfigOut, error) { // // state should be the next state required // Default should be the default state +// name is the config name for this item // help should be the help shown to the user -func ConfigConfirm(state string, Default bool, help string) (*ConfigOut, error) { +func ConfigConfirm(state string, Default bool, name string, help string) (*ConfigOut, error) { return &ConfigOut{ State: state, Option: &Option{ + Name: name, Help: help, Default: Default, Examples: []OptionExample{{ @@ -151,19 +168,21 @@ func ConfigConfirm(state string, Default bool, help string) (*ConfigOut, error) // ConfigChooseFixed returns a ConfigOut structure which has a list of items to choose from. // // state should be the next state required +// name is the config name for this item // help should be the help shown to the user // items should be the items in the list // // It chooses the first item to be the default. // If there are no items then it will return an error. // If there is only one item it will short cut to the next state -func ConfigChooseFixed(state string, help string, items []OptionExample) (*ConfigOut, error) { +func ConfigChooseFixed(state string, name string, help string, items []OptionExample) (*ConfigOut, error) { if len(items) == 0 { return nil, errors.Errorf("no items found in: %s", help) } choose := &ConfigOut{ State: state, Option: &Option{ + Name: name, Help: help, Examples: items, }, @@ -180,6 +199,7 @@ func ConfigChooseFixed(state string, help string, items []OptionExample) (*Confi // ConfigChoose returns a ConfigOut structure which has a list of items to choose from. // // state should be the next state required +// name is the config name for this item // help should be the help shown to the user // n should be the number of items in the list // getItem should return the items (value, help) @@ -187,12 +207,12 @@ func ConfigChooseFixed(state string, help string, items []OptionExample) (*Confi // It chooses the first item to be the default. // If there are no items then it will return an error. // If there is only one item it will short cut to the next state -func ConfigChoose(state string, help string, n int, getItem func(i int) (itemValue string, itemHelp string)) (*ConfigOut, error) { +func ConfigChoose(state string, name string, help string, n int, getItem func(i int) (itemValue string, itemHelp string)) (*ConfigOut, error) { items := make(OptionExamples, n) for i := range items { items[i].Value, items[i].Help = getItem(i) } - return ConfigChooseFixed(state, help, items) + return ConfigChooseFixed(state, name, help, items) } // StatePush pushes a new values onto the front of the config string @@ -237,21 +257,21 @@ func StatePop(state string) (newState string, value string) { // BackendConfig calls the config for the backend in ri // // It wraps any OAuth transactions as necessary so only straight forward config questions are emitted -func BackendConfig(ctx context.Context, name string, m configmap.Mapper, ri *RegInfo, in ConfigIn) (*ConfigOut, error) { +func BackendConfig(ctx context.Context, name string, m configmap.Mapper, ri *RegInfo, in ConfigIn) (out *ConfigOut, err error) { ci := GetConfig(ctx) if ri.Config == nil { return nil, nil } - // Do internal states here - if strings.HasPrefix(in.State, "*") { - switch { - case strings.HasPrefix(in.State, "*oauth"): - return ConfigOAuth(ctx, name, m, ri, in) - default: - return nil, errors.Errorf("unknown internal state %q", in.State) - } + switch { + case strings.HasPrefix(in.State, "*oauth"): + // Do internal oauth states + out, err = ConfigOAuth(ctx, name, m, ri, in) + case strings.HasPrefix(in.State, "*"): + err = errors.Errorf("unknown internal state %q", in.State) + default: + // Otherwise pass to backend + out, err = ri.Config(ctx, name, m, in) } - out, err := ri.Config(ctx, name, m, in) if err != nil { return nil, err } @@ -267,11 +287,21 @@ func BackendConfig(ctx context.Context, name string, m configmap.Mapper, ri *Reg } // Run internal state, saving the input so we can recall the state return ConfigGoto(StatePush("", "*oauth", returnState, in.State, in.Result)) - case out.Option != nil && ci.AutoConfirm: + case out.Option != nil: + if out.Option.Name == "" { + return nil, errors.New("internal error: no name set in Option") + } + // If override value is set in the config then use that + if result, ok := m.Get(out.Option.Name); ok { + Debugf(nil, "Override value found, choosing value %q for state %q", result, out.State) + return ConfigResult(out.State, result) + } // If AutoConfirm is set, choose the default value - result := fmt.Sprint(out.Option.Default) - Debugf(nil, "Auto confirm is set, choosing default %q for state %q", result, out.State) - return ConfigResult(out.State, result) + if ci.AutoConfirm { + result := fmt.Sprint(out.Option.Default) + Debugf(nil, "Auto confirm is set, choosing default %q for state %q", result, out.State) + return ConfigResult(out.State, result) + } } return out, nil } diff --git a/fs/config/ui.go b/fs/config/ui.go index 997fb0eec..e78a2e876 100644 --- a/fs/config/ui.go +++ b/fs/config/ui.go @@ -245,7 +245,6 @@ func OkRemote(name string) bool { // // The is the user interface loop that drives the post configuration backend config. func PostConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.RegInfo) error { - // FIXME if doing authorize, stop when we've got to the OAuth if ri.Config == nil { return errors.New("backend doesn't support reconnect or authorize") } @@ -267,6 +266,7 @@ func PostConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.Reg in.State = out.State in.Result = out.Result if out.Option != nil { + fs.Debugf(name, "config: reading config item named %q", out.Option.Name) if out.Option.Default == nil { out.Option.Default = "" } diff --git a/lib/oauthutil/oauthutil.go b/lib/oauthutil/oauthutil.go index 6e3b15cae..90cf7b02b 100644 --- a/lib/oauthutil/oauthutil.go +++ b/lib/oauthutil/oauthutil.go @@ -455,14 +455,14 @@ func ConfigOAuth(ctx context.Context, name string, m configmap.Mapper, ri *fs.Re // See if already have a token tokenString, ok := m.Get("token") if ok && tokenString != "" { - return fs.ConfigConfirm(newState("*oauth-confirm"), true, "Already have a token - refresh?") + return fs.ConfigConfirm(newState("*oauth-confirm"), true, "config_refresh_token", "Already have a token - refresh?") } return fs.ConfigGoto(newState("*oauth-confirm")) case "*oauth-confirm": if in.Result == "false" { return fs.ConfigGoto(newState("*oauth-done")) } - return fs.ConfigConfirm(newState("*oauth-islocal"), true, "Use auto config?\n * Say Y if not sure\n * Say N if you are working on a remote or headless machine\n") + return fs.ConfigConfirm(newState("*oauth-islocal"), true, "config_is_local", "Use auto config?\n * Say Y if not sure\n * Say N if you are working on a remote or headless machine\n") case "*oauth-islocal": if in.Result == "true" { return fs.ConfigGoto(newState("*oauth-do")) @@ -478,7 +478,7 @@ func ConfigOAuth(ctx context.Context, name string, m configmap.Mapper, ri *fs.Re if err != nil { return nil, err } - return fs.ConfigInput(newState("*oauth-do"), fmt.Sprintf("Verification code\n\nGo to this URL, authenticate then paste the code here.\n\n%s\n", authURL)) + return fs.ConfigInput(newState("*oauth-do"), "config_verification_code", fmt.Sprintf("Verification code\n\nGo to this URL, authenticate then paste the code here.\n\n%s\n", authURL)) } var out strings.Builder fmt.Fprintf(&out, `For this to work, you will need rclone available on a machine that has @@ -508,7 +508,7 @@ version recommended): fmt.Fprintf(&out, "\trclone authorize %q\n", ri.Name) } fmt.Fprintln(&out, "\nThen paste the result.") - return fs.ConfigInput(newState("*oauth-authorize"), out.String()) + return fs.ConfigInput(newState("*oauth-authorize"), "config_token", out.String()) case "*oauth-authorize": // Read the updates to the config outM := configmap.Simple{}