From d31d86dc7d38fbcf523741a7be436af126066992 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 15 Apr 2025 10:13:56 -0400 Subject: [PATCH 01/55] feat: add rclone --- plugin/plugins/dynamix.unraid.net.plg | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 5e317cd4cf..6a614ae6cf 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -52,6 +52,27 @@ exit 0 &vendor_store_url; + + + RCLONE_ZIP_FILENAME="&RCLONE_ZIP_FILENAME;" + RCLONE_DIR="&RCLONE_DIR;" + RCLONE_VERSION="&RCLONE_VERSION;" + + + &txz_url; From 8df0ca58b5597083c1fa523b15a26dd09a07274f Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 15 Apr 2025 14:57:47 -0400 Subject: [PATCH 02/55] chore: progress on rclone --- api/.env.development | 2 + api/generated-schema.graphql | 106 +- api/src/store/modules/paths.ts | 7 +- api/src/unraid-api/auth/auth.service.ts | 2 +- .../connect/connect-settings.resolver.ts | 6 +- .../connect/connect-settings.service.ts | 4 +- .../graph/resolvers/docker/docker.model.ts | 18 +- .../flash-backup/flash-backup.model.ts | 53 + .../flash-backup/flash-backup.module.ts | 11 + .../flash-backup/flash-backup.resolver.ts | 25 + .../resolvers/rclone/jsonforms/config.ts | 4237 +++++++++++++++++ .../jsonforms/rclone-jsonforms-config.ts | 427 ++ .../resolvers/rclone/rclone-api.service.ts | 272 ++ .../rclone/rclone-config.resolver.ts | 64 + .../resolvers/rclone/rclone-form.service.ts | 118 + .../graph/resolvers/rclone/rclone.model.ts | 199 + .../graph/resolvers/rclone/rclone.module.ts | 20 + .../graph/resolvers/rclone/rclone.resolver.ts | 69 + .../graph/resolvers/rclone/rclone.service.ts | 89 + .../graph/resolvers/resolvers.module.ts | 17 +- api/src/unraid-api/rest/rest.controller.ts | 50 +- api/src/unraid-api/rest/rest.module.ts | 4 +- web/components/RClone/RCloneConfig.vue | 216 + .../RClone/graphql/settings.query.ts | 35 + web/composables/gql/gql.ts | 18 + web/composables/gql/graphql.ts | 137 +- web/helpers/create-apollo-client.ts | 1 + web/pages/flashbackup.vue | 24 + 28 files changed, 6182 insertions(+), 49 deletions(-) create mode 100644 api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts create mode 100644 api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts create mode 100644 api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/jsonforms/config.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts create mode 100644 api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts create mode 100644 web/components/RClone/RCloneConfig.vue create mode 100644 web/components/RClone/graphql/settings.query.ts create mode 100644 web/pages/flashbackup.vue diff --git a/api/.env.development b/api/.env.development index 8cd6451e7c..949bdc97ea 100644 --- a/api/.env.development +++ b/api/.env.development @@ -13,6 +13,8 @@ PATHS_PARITY_CHECKS=./dev/states/parity-checks.log PATHS_CONFIG_MODULES=./dev/configs PATHS_ACTIVATION_BASE=./dev/activation PATHS_PASSWD=./dev/passwd +PATHS_RCLONE_SOCKET=./dev/rclone-socket +PATHS_LOG_BASE=./dev/log # Where we store logs ENVIRONMENT="development" NODE_ENV="development" PORT="3001" diff --git a/api/generated-schema.graphql b/api/generated-schema.graphql index 6938cf1eb4..53ae1e3b8c 100644 --- a/api/generated-schema.graphql +++ b/api/generated-schema.graphql @@ -1299,20 +1299,15 @@ type DockerContainer implements Node { """Total size of all the files in the container""" sizeRootFs: Int - labels: JSONObject + labels: JSON state: ContainerState! status: String! hostConfig: ContainerHostConfig - networkSettings: JSONObject - mounts: [JSONObject!] + networkSettings: JSON + mounts: [JSON!] autoStart: Boolean! } -""" -The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). -""" -scalar JSONObject @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") - enum ContainerState { RUNNING EXITED @@ -1325,15 +1320,15 @@ type DockerNetwork implements Node { scope: String! driver: String! enableIPv6: Boolean! - ipam: JSONObject! + ipam: JSON! internal: Boolean! attachable: Boolean! ingress: Boolean! - configFrom: JSONObject! + configFrom: JSON! configOnly: Boolean! - containers: JSONObject! - options: JSONObject! - labels: JSONObject! + containers: JSON! + options: JSON! + labels: JSON! } type Docker implements Node { @@ -1342,6 +1337,64 @@ type Docker implements Node { networks(skipCache: Boolean! = false): [DockerNetwork!]! } +type FlashBackupStatus { + """Status message indicating the outcome of the backup initiation.""" + status: String! + + """Job ID if available, can be used to check job status.""" + jobId: String +} + +type RCloneDrive { + """Provider name""" + name: String! + + """Provider options and configuration schema""" + options: JSON! +} + +type RCloneProviderOption { + name: String! + help: String! + provider: String! + default: JSON + value: JSON + shortOpt: String + hide: Boolean + required: Boolean + isPassword: Boolean + noPrefix: Boolean + advanced: Boolean + defaultStr: String + valueStr: String + type: String + examples: [RCloneProviderOptionExample!] +} + +type RCloneProviderOptionExample { + value: String! + help: String! + provider: String! +} + +type RCloneBackupConfigForm { + id: ID! + dataSchema: JSON! + uiSchema: JSON! +} + +type RCloneBackupSettings { + configForm: RCloneBackupConfigForm! + drives: [RCloneDrive!]! + remotes: [String!]! +} + +type RCloneRemote { + name: String! + type: String! + config: JSON! +} + type Flash implements Node { id: PrefixedID! guid: String! @@ -1543,6 +1596,7 @@ type Query { docker: Docker! disks: [Disk!]! disk(id: PrefixedID!): Disk! + rcloneBackup: RCloneBackupSettings! health: String! getDemo: String! } @@ -1578,6 +1632,10 @@ type Mutation { setupRemoteAccess(input: SetupRemoteAccessInput!): Boolean! setAdditionalAllowedOrigins(input: AllowedOriginInput!): [String!]! enableDynamicRemoteAccess(input: EnableDynamicRemoteAccessInput!): Boolean! + + """Initiates a flash drive backup using a configured remote.""" + initiateFlashBackup(input: InitiateFlashBackupInput!): FlashBackupStatus! + createRCloneRemote(input: CreateRCloneRemoteInput!): RCloneRemote! setDemo: String! } @@ -1674,6 +1732,28 @@ input AccessUrlInput { ipv6: URL } +input InitiateFlashBackupInput { + """The name of the remote configuration to use for the backup.""" + remoteName: String! + + """Source path to backup (typically the flash drive).""" + sourcePath: String! + + """Destination path on the remote.""" + destinationPath: String! + + """ + Additional options for the backup operation, such as --dry-run or --transfers. + """ + options: JSON +} + +input CreateRCloneRemoteInput { + name: String! + type: String! + config: JSON! +} + type Subscription { displaySubscription: Display! infoSubscription: Info! diff --git a/api/src/store/modules/paths.ts b/api/src/store/modules/paths.ts index 2d4c75f9ee..41a9e6a237 100644 --- a/api/src/store/modules/paths.ts +++ b/api/src/store/modules/paths.ts @@ -21,6 +21,9 @@ const initialState = { ), 'docker-autostart': '/var/lib/docker/unraid-autostart' as const, 'docker-socket': '/var/run/docker.sock' as const, + 'rclone-socket': resolvePath( + process.env.PATHS_RCLONE_SOCKET ?? ('/var/run/rclone.socket' as const) + ), 'parity-checks': resolvePath( process.env.PATHS_PARITY_CHECKS ?? ('/boot/config/parity-checks.log' as const) ), @@ -54,8 +57,8 @@ const initialState = { ('/boot/config/plugins/dynamix.my.servers/fb_keepalive' as const), 'keyfile-base': resolvePath(process.env.PATHS_KEYFILE_BASE ?? ('/boot/config' as const)), 'machine-id': resolvePath(process.env.PATHS_MACHINE_ID ?? ('/var/lib/dbus/machine-id' as const)), - 'log-base': resolvePath('/var/log/unraid-api/' as const), - 'unraid-log-base': resolvePath('/var/log/' as const), + 'log-base': process.env.PATHS_LOG_BASE ?? resolvePath('/var/log/unraid-api/' as const), + 'unraid-log-base': process.env.PATHS_UNRAID_LOG_BASE ?? resolvePath('/var/log/' as const), 'var-run': '/var/run' as const, // contains sess_ files that correspond to authenticated user sessions 'auth-sessions': process.env.PATHS_AUTH_SESSIONS ?? '/var/lib/php', diff --git a/api/src/unraid-api/auth/auth.service.ts b/api/src/unraid-api/auth/auth.service.ts index 526c8f9e8b..f5792597cc 100644 --- a/api/src/unraid-api/auth/auth.service.ts +++ b/api/src/unraid-api/auth/auth.service.ts @@ -52,7 +52,7 @@ export class AuthService { async validateCookiesWithCsrfToken(request: FastifyRequest): Promise { try { - if (!this.validateCsrfToken(request.headers['x-csrf-token'] || request.query.csrf_token)) { + if (request.method !== 'GET' && !request.url.startsWith('/graphql/api/rclone-webgui/') && (!this.validateCsrfToken(request.headers['x-csrf-token'] || request.query.csrf_token))) { throw new UnauthorizedException('Invalid CSRF token'); } diff --git a/api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts b/api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts index 163bf05ddc..ae720c5612 100644 --- a/api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts @@ -1,8 +1,8 @@ import { Logger } from '@nestjs/common'; import { Args, Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql'; -import { Layout } from '@jsonforms/core'; -import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars'; +import { type Layout } from '@jsonforms/core'; +import { GraphQLJSON } from 'graphql-scalars'; import { getAllowedOrigins } from '@app/common/allowed-origins.js'; import { setupRemoteAccessThunk } from '@app/store/actions/setup-remote-access.js'; @@ -46,7 +46,7 @@ export class ConnectSettingsResolver { }; } - @ResolveField(() => GraphQLJSONObject) + @ResolveField(() => GraphQLJSON) public async uiSchema(): Promise { const { elements } = await this.connectSettingsService.buildSettingsSchema(); return { diff --git a/api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts b/api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts index 6f2c135d7a..17fb788815 100644 --- a/api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts +++ b/api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts @@ -95,8 +95,8 @@ export class ConnectSettingsService { getState: store.getState, dispatch: store.dispatch, }); - } - return true; + } + return true; } async isSignedIn(): Promise { diff --git a/api/src/unraid-api/graph/resolvers/docker/docker.model.ts b/api/src/unraid-api/graph/resolvers/docker/docker.model.ts index 2dded5521d..8c0ec2277c 100644 --- a/api/src/unraid-api/graph/resolvers/docker/docker.model.ts +++ b/api/src/unraid-api/graph/resolvers/docker/docker.model.ts @@ -1,6 +1,6 @@ import { Field, ID, Int, ObjectType, registerEnumType } from '@nestjs/graphql'; -import { GraphQLJSONObject, GraphQLPort } from 'graphql-scalars'; +import { GraphQLJSON, GraphQLPort } from 'graphql-scalars'; import { Node } from '@app/unraid-api/graph/resolvers/base.model.js'; @@ -93,7 +93,7 @@ export class DockerContainer extends Node { @Field(() => Int, { nullable: true, description: 'Total size of all the files in the container' }) sizeRootFs?: number; - @Field(() => GraphQLJSONObject, { nullable: true }) + @Field(() => GraphQLJSON, { nullable: true }) labels?: Record; @Field(() => ContainerState) @@ -105,10 +105,10 @@ export class DockerContainer extends Node { @Field(() => ContainerHostConfig, { nullable: true }) hostConfig?: ContainerHostConfig; - @Field(() => GraphQLJSONObject, { nullable: true }) + @Field(() => GraphQLJSON, { nullable: true }) networkSettings?: Record; - @Field(() => [GraphQLJSONObject], { nullable: true }) + @Field(() => [GraphQLJSON], { nullable: true }) mounts?: Record[]; @Field(() => Boolean) @@ -132,7 +132,7 @@ export class DockerNetwork extends Node { @Field(() => Boolean) enableIPv6!: boolean; - @Field(() => GraphQLJSONObject) + @Field(() => GraphQLJSON) ipam!: Record; @Field(() => Boolean) @@ -144,19 +144,19 @@ export class DockerNetwork extends Node { @Field(() => Boolean) ingress!: boolean; - @Field(() => GraphQLJSONObject) + @Field(() => GraphQLJSON) configFrom!: Record; @Field(() => Boolean) configOnly!: boolean; - @Field(() => GraphQLJSONObject) + @Field(() => GraphQLJSON) containers!: Record; - @Field(() => GraphQLJSONObject) + @Field(() => GraphQLJSON) options!: Record; - @Field(() => GraphQLJSONObject) + @Field(() => GraphQLJSON) labels!: Record; } diff --git a/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts b/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts new file mode 100644 index 0000000000..5013eaf3ab --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts @@ -0,0 +1,53 @@ +import { Field, InputType, ObjectType } from '@nestjs/graphql'; + +import { GraphQLJSON } from 'graphql-scalars'; + +@InputType() +export class InitiateFlashBackupInput { + @Field(() => String, { description: 'The name of the remote configuration to use for the backup.' }) + remoteName!: string; + + @Field(() => String, { description: 'Source path to backup (typically the flash drive).' }) + sourcePath!: string; + + @Field(() => String, { description: 'Destination path on the remote.' }) + destinationPath!: string; + + @Field(() => GraphQLJSON, { + description: 'Additional options for the backup operation, such as --dry-run or --transfers.', + nullable: true, + }) + options?: Record; +} + +@ObjectType() +export class FlashBackupStatus { + @Field(() => String, { + description: 'Status message indicating the outcome of the backup initiation.', + }) + status!: string; + + @Field(() => String, { + description: 'Job ID if available, can be used to check job status.', + nullable: true, + }) + jobId?: string; +} + +@ObjectType() +export class FlashBackupJob { + @Field(() => String, { description: 'Job ID' }) + id!: string; + + @Field(() => String, { description: 'Job type (e.g., sync/copy)' }) + type!: string; + + @Field(() => GraphQLJSON, { description: 'Job status and statistics' }) + stats!: Record; +} + +@ObjectType() +export class RCloneWebGuiInfo { + @Field() + url!: string; +} diff --git a/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts b/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts new file mode 100644 index 0000000000..c440473fdb --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { FlashBackupResolver } from './flash-backup.resolver.js'; +import { RCloneModule } from '@app/unraid-api/graph/resolvers/rclone/rclone.module.js'; + +@Module({ + imports: [RCloneModule], + providers: [FlashBackupResolver], + exports: [], +}) +export class FlashBackupModule {} diff --git a/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts b/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts new file mode 100644 index 0000000000..a570179c96 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts @@ -0,0 +1,25 @@ +import { Inject, Logger } from '@nestjs/common'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; + + +import { + FlashBackupStatus, + InitiateFlashBackupInput, +} from './flash-backup.model.js'; +import { RCloneService } from '@app/unraid-api/graph/resolvers/rclone/rclone.service.js'; + +@Resolver() +export class FlashBackupResolver { + private readonly logger = new Logger(FlashBackupResolver.name); + + constructor(private readonly rcloneService: RCloneService) {} + + @Mutation(() => FlashBackupStatus, { + description: 'Initiates a flash drive backup using a configured remote.', + }) + async initiateFlashBackup( + @Args('input') input: InitiateFlashBackupInput + ): Promise { + throw new Error('Not implemented'); + } +} diff --git a/api/src/unraid-api/graph/resolvers/rclone/jsonforms/config.ts b/api/src/unraid-api/graph/resolvers/rclone/jsonforms/config.ts new file mode 100644 index 0000000000..4e6ede8029 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/jsonforms/config.ts @@ -0,0 +1,4237 @@ +/** + * DO NOT MODIFY - Directly from : https://raw.githubusercontent.com/rclone/rclone-webui-react/master/src/views/RemoteManagement/NewDrive/config.js + */ + +export const config = [ + { + Name: 'alias', + Description: 'Alias for a existing remote', + Prefix: 'alias', + Options: [ + { + Name: 'remote', + Help: 'Remote or path to alias.\nCan be "myremote:path/to/dir", "myremote:bucket", "myremote:" or "/local/path".', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'amazon cloud drive', + Description: 'Amazon Drive', + Prefix: 'acd', + Options: [ + { + Name: 'client_id', + Help: 'Amazon Application Client ID.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Amazon Application Client Secret.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'auth_url', + Help: "Auth server URL.\nLeave blank to use Amazon's.", + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'token_url', + Help: "Token server url.\nleave blank to use Amazon's.", + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'checkpoint', + Help: 'Checkpoint for internal polling (debug).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 3, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'upload_wait_per_gb', + Help: 'Additional time per GB to wait after a failed complete upload to see if it appears.\n\nSometimes Amazon Drive gives an error when a file has been fully\nuploaded but the file appears anyway after a little while. This\nhappens sometimes for files over 1GB in size and nearly every time for\nfiles bigger than 10GB. This parameter controls the time rclone waits\nfor the file to appear.\n\nThe default value for this parameter is 3 minutes per GB, so by\ndefault it will wait 3 minutes for every GB uploaded to see if the\nfile appears.\n\nYou can disable this feature by setting it to 0. This may cause\nconflict errors as rclone retries the failed upload but the file will\nmost likely appear correctly eventually.\n\nThese values were determined empirically by observing lots of uploads\nof big files for a range of file sizes.\n\nUpload with the "-v" flag to see more info about what rclone is doing\nin this situation.', + Provider: '', + Default: 180000000000, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'templink_threshold', + Help: 'Files \u003e= this size will be downloaded via their tempLink.\n\nFiles this size or more will be downloaded via their "tempLink". This\nis to work around a problem with Amazon Drive which blocks downloads\nof files bigger than about 10GB. The default for this is 9GB which\nshouldn\'t need to be changed.\n\nTo download files above this threshold, rclone requests a "tempLink"\nwhich downloads the file through a temporary URL directly from the\nunderlying S3 storage.', + Provider: '', + Default: 9663676416, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'azureblob', + Description: 'Microsoft Azure Blob Storage', + Prefix: 'azureblob', + Options: [ + { + Name: 'account', + Help: 'Storage Account Name (leave blank to use connection string or SAS URL)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'key', + Help: 'Storage Account Key (leave blank to use connection string or SAS URL)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'sas_url', + Help: 'SAS URL for container level access only\n(leave blank if using account/key or connection string)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint', + Help: 'Endpoint for the service\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'upload_cutoff', + Help: 'Cutoff for switching to chunked upload (\u003c= 256MB).', + Provider: '', + Default: 268435456, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_size', + Help: 'Upload chunk size (\u003c= 100MB).\n\nNote that this is stored in memory and there may be up to\n"--transfers" chunks stored at once in memory.', + Provider: '', + Default: 4194304, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'list_chunk', + Help: 'Size of blob list.\n\nThis sets the number of blobs requested in each listing chunk. Default\nis the maximum, 5000. "List blobs" requests are permitted 2 minutes\nper megabyte to complete. If an operation is taking longer than 2\nminutes per megabyte on average, it will time out (\n[source](https://docs.microsoft.com/en-us/rest/api/storageservices/setting-timeouts-for-blob-service-operations#exceptions-to-default-timeout-interval)\n). This can be used to limit the number of blobs items to return, to\navoid the time out.', + Provider: '', + Default: 5000, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'access_tier', + Help: 'Access tier of blob: hot, cool or archive.\n\nArchived blobs can be restored by setting access tier to hot or\ncool. Leave blank if you intend to use default access tier, which is\nset at account level\n\nIf there is no "access tier" specified, rclone doesn\'t apply any tier.\nrclone performs "Set Tier" operation on blobs while uploading, if objects\nare not modified, specifying "access tier" to new one will have no effect.\nIf blobs are in "archive tier" at remote, trying to perform data transfer\noperations from remote will not be allowed. User should first restore by\ntiering blob to "Hot" or "Cool".', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'b2', + Description: 'Backblaze B2', + Prefix: 'b2', + Options: [ + { + Name: 'account', + Help: 'Account ID or Application Key ID', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'key', + Help: 'Application Key', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint', + Help: 'Endpoint for the service.\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'test_mode', + Help: 'A flag string for X-Bz-Test-Mode header for debugging.\n\nThis is for debugging purposes only. Setting it to one of the strings\nbelow will cause b2 to return specific errors:\n\n * "fail_some_uploads"\n * "expire_some_account_authorization_tokens"\n * "force_cap_exceeded"\n\nThese will be set in the "X-Bz-Test-Mode" header which is documented\nin the [b2 integrations checklist](https://www.backblaze.com/b2/docs/integration_checklist.html).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 2, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'versions', + Help: "Include old versions in directory listings.\nNote that when using this no file write operations are permitted,\nso you can't upload files or delete them.", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'hard_delete', + Help: 'Permanently delete files on remote removal, otherwise hide files.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'upload_cutoff', + Help: 'Cutoff for switching to chunked upload.\n\nFiles above this size will be uploaded in chunks of "--b2-chunk-size".\n\nThis value should be set no larger than 4.657GiB (== 5GB).', + Provider: '', + Default: 209715200, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_size', + Help: 'Upload chunk size. Must fit in memory.\n\nWhen uploading large files, chunk the file into this size. Note that\nthese chunks are buffered in memory and there might a maximum of\n"--transfers" chunks in progress at once. 5,000,000 Bytes is the\nminimum size.', + Provider: '', + Default: 100663296, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'disable_checksum', + Help: 'Disable checksums for large (\u003e upload cutoff) files', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'box', + Description: 'Box', + Prefix: 'box', + Options: [ + { + Name: 'client_id', + Help: 'Box App Client Id.\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Box App Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'upload_cutoff', + Help: 'Cutoff for switching to multipart upload (\u003e= 50MB).', + Provider: '', + Default: 52428800, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'commit_retries', + Help: 'Max number of times to try committing a multipart file.', + Provider: '', + Default: 100, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'crypt', + Description: 'Encrypt/Decrypt a remote', + Prefix: 'crypt', + Options: [ + { + Name: 'remote', + Help: 'Remote to encrypt/decrypt.\nNormally should contain a \':\' and a path, eg "myremote:path/to/dir",\n"myremote:bucket" or maybe "myremote:" (not recommended).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'filename_encryption', + Help: 'How to encrypt the filenames.', + Provider: '', + Default: 'standard', + Value: null, + Examples: [ + { + Value: 'off', + Help: 'Don\'t encrypt the file names. Adds a ".bin" extension only.', + Provider: '', + }, + { + Value: 'standard', + Help: 'Encrypt the filenames see the docs for the details.', + Provider: '', + }, + { + Value: 'obfuscate', + Help: 'Very simple filename obfuscation.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'directory_name_encryption', + Help: 'Option to either encrypt directory names or leave them intact.', + Provider: '', + Default: true, + Value: null, + Examples: [ + { + Value: 'true', + Help: 'Encrypt directory names.', + Provider: '', + }, + { + Value: 'false', + Help: "Don't encrypt directory names, leave them intact.", + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'password', + Help: 'Password or pass phrase for encryption.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'password2', + Help: 'Password or pass phrase for salt. Optional but recommended.\nShould be different to the previous password.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'show_mapping', + Help: 'For all files listed show how the names encrypt.\n\nIf this flag is set then for each file that the remote is asked to\nlist, it will log (at level INFO) a line stating the decrypted file\nname and the encrypted file name.\n\nThis is so you can work out which encrypted names are which decrypted\nnames just in case you need to do something with the encrypted file\nnames, or for debugging purposes.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 2, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'cache', + Description: 'Cache a remote', + Prefix: 'cache', + Options: [ + { + Name: 'remote', + Help: 'Remote to cache.\nNormally should contain a \':\' and a path, eg "myremote:path/to/dir",\n"myremote:bucket" or maybe "myremote:" (not recommended).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'plex_url', + Help: 'The URL of the Plex server', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'plex_username', + Help: 'The username of the Plex user', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'plex_password', + Help: 'The password of the Plex user', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'plex_token', + Help: 'The plex token for authentication - auto set normally', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 3, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'plex_insecure', + Help: 'Skip all certificate verifications when connecting to the Plex server', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_size', + Help: 'The size of a chunk (partial file data).\n\nUse lower numbers for slower connections. If the chunk size is\nchanged, any downloaded chunks will be invalid and cache-chunk-path\nwill need to be cleared or unexpected EOF errors will occur.', + Provider: '', + Default: 5242880, + Value: null, + Examples: [ + { + Value: '1m', + Help: '1MB', + Provider: '', + }, + { + Value: '5M', + Help: '5 MB', + Provider: '', + }, + { + Value: '10M', + Help: '10 MB', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'info_age', + Help: 'How long to cache file structure information (directory listings, file size, times etc). \nIf all write operations are done through the cache then you can safely make\nthis value very large as the cache store will also be updated in real time.', + Provider: '', + Default: 21600000000000, + Value: null, + Examples: [ + { + Value: '1h', + Help: '1 hour', + Provider: '', + }, + { + Value: '24h', + Help: '24 hours', + Provider: '', + }, + { + Value: '48h', + Help: '48 hours', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'chunk_total_size', + Help: 'The total size that the chunks can take up on the local disk.\n\nIf the cache exceeds this value then it will start to delete the\noldest chunks until it goes under this value.', + Provider: '', + Default: 10737418240, + Value: null, + Examples: [ + { + Value: '500M', + Help: '500 MB', + Provider: '', + }, + { + Value: '1G', + Help: '1 GB', + Provider: '', + }, + { + Value: '10G', + Help: '10 GB', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'db_path', + Help: 'Directory to store file structure metadata DB.\nThe remote name is used as the DB file name.', + Provider: '', + Default: '/home/negative0/.cache/rclone/cache-backend', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_path', + Help: 'Directory to cache chunk files.\n\nPath to where partial file data (chunks) are stored locally. The remote\nname is appended to the final path.\n\nThis config follows the "--cache-db-path". If you specify a custom\nlocation for "--cache-db-path" and don\'t specify one for "--cache-chunk-path"\nthen "--cache-chunk-path" will use the same path as "--cache-db-path".', + Provider: '', + Default: '/home/negative0/.cache/rclone/cache-backend', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'db_purge', + Help: 'Clear all the cached data for this remote on start.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 2, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_clean_interval', + Help: 'How often should the cache perform cleanups of the chunk storage.\nThe default value should be ok for most people. If you find that the\ncache goes over "cache-chunk-total-size" too often then try to lower\nthis value to force it to perform cleanups more often.', + Provider: '', + Default: 60000000000, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'read_retries', + Help: "How many times to retry a read from a cache storage.\n\nSince reading from a cache stream is independent from downloading file\ndata, readers can get to a point where there's no more data in the\ncache. Most of the times this can indicate a connectivity issue if\ncache isn't able to provide file data anymore.\n\nFor really slow connections, increase this to a point where the stream is\nable to provide data but your experience will be very stuttering.", + Provider: '', + Default: 10, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'workers', + Help: 'How many workers should run in parallel to download chunks.\n\nHigher values will mean more parallel processing (better CPU needed)\nand more concurrent requests on the cloud provider. This impacts\nseveral aspects like the cloud provider API limits, more stress on the\nhardware that rclone runs on but it also means that streams will be\nmore fluid and data will be available much more faster to readers.\n\n**Note**: If the optional Plex integration is enabled then this\nsetting will adapt to the type of reading performed and the value\nspecified here will be used as a maximum number of workers to use.', + Provider: '', + Default: 4, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_no_memory', + Help: 'Disable the in-memory cache for storing chunks during streaming.\n\nBy default, cache will keep file data during streaming in RAM as well\nto provide it to readers as fast as possible.\n\nThis transient data is evicted as soon as it is read and the number of\nchunks stored doesn\'t exceed the number of workers. However, depending\non other settings like "cache-chunk-size" and "cache-workers" this footprint\ncan increase if there are parallel streams too (multiple files being read\nat the same time).\n\nIf the hardware permits it, use this feature to provide an overall better\nperformance during streaming but it can also be disabled if RAM is not\navailable on the local machine.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'rps', + Help: "Limits the number of requests per second to the source FS (-1 to disable)\n\nThis setting places a hard limit on the number of requests per second\nthat cache will be doing to the cloud provider remote and try to\nrespect that value by setting waits between reads.\n\nIf you find that you're getting banned or limited on the cloud\nprovider through cache and know that a smaller number of requests per\nsecond will allow you to work with it then you can use this setting\nfor that.\n\nA good balance of all the other settings should make this setting\nuseless but it is available to set for more special cases.\n\n**NOTE**: This will limit the number of requests during streams but\nother API calls to the cloud provider like directory listings will\nstill pass.", + Provider: '', + Default: -1, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'writes', + Help: 'Cache file data on writes through the FS\n\nIf you need to read files immediately after you upload them through\ncache you can enable this flag to have their data stored in the\ncache store at the same time during upload.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'tmp_upload_path', + Help: 'Directory to keep temporary files until they are uploaded.\n\nThis is the path where cache will use as a temporary storage for new\nfiles that need to be uploaded to the cloud provider.\n\nSpecifying a value will enable this feature. Without it, it is\ncompletely disabled and files will be uploaded directly to the cloud\nprovider', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'tmp_wait_time', + Help: 'How long should files be stored in local cache before being uploaded\n\nThis is the duration that a file must wait in the temporary location\n_cache-tmp-upload-path_ before it is selected for upload.\n\nNote that only one file is uploaded at a time and it can take longer\nto start the upload if a queue formed for this purpose.', + Provider: '', + Default: 15000000000, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'db_wait_time', + Help: 'How long to wait for the DB to be available - 0 is unlimited\n\nOnly one process can have the DB open at any one time, so rclone waits\nfor this duration for the DB to become available before it gives an\nerror.\n\nIf you set it to 0 then it will wait forever.', + Provider: '', + Default: 1000000000, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'drive', + Description: 'Google Drive', + Prefix: 'drive', + Options: [ + { + Name: 'client_id', + Help: 'Google Application Client Id\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Google Application Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'scope', + Help: 'Scope that rclone should use when requesting access from drive.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'drive', + Help: 'Full access all files, excluding Application Data Folder.', + Provider: '', + }, + { + Value: 'drive.readonly', + Help: 'Read-only access to file metadata and file contents.', + Provider: '', + }, + { + Value: 'drive.file', + Help: 'Access to files created by rclone only.\nThese are visible in the drive website.\nFile authorization is revoked when the user deauthorizes the app.', + Provider: '', + }, + { + Value: 'drive.appfolder', + Help: 'Allows read and write access to the Application Data folder.\nThis is not visible in the drive website.', + Provider: '', + }, + { + Value: 'drive.metadata.readonly', + Help: 'Allows read-only access to file metadata but\ndoes not allow any access to read or download file content.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'root_folder_id', + Help: 'ID of the root folder\nLeave blank normally.\nFill in to access "Computers" folders. (see docs).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'service_account_file', + Help: 'Service Account Credentials JSON file path \nLeave blank normally.\nNeeded only if you want use SA instead of interactive login.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'service_account_credentials', + Help: 'Service Account Credentials JSON blob\nLeave blank normally.\nNeeded only if you want use SA instead of interactive login.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 2, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'team_drive', + Help: 'ID of the Team Drive', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 2, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'auth_owner_only', + Help: 'Only consider files owned by the authenticated user.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'use_trash', + Help: 'Send files to the trash instead of deleting permanently.\nDefaults to true, namely sending files to the trash.\nUse `--drive-use-trash=false` to delete files permanently instead.', + Provider: '', + Default: true, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'skip_gdocs', + Help: 'Skip google documents in all listings.\nIf given, gdocs practically become invisible to rclone.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'shared_with_me', + Help: 'Only show files that are shared with me.\n\nInstructs rclone to operate on your "Shared with me" folder (where\nGoogle Drive lets you access the files and folders others have shared\nwith you).\n\nThis works both with the "list" (lsd, lsl, etc) and the "copy"\ncommands (copy, sync, etc), and with all other commands too.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'trashed_only', + Help: 'Only show files that are in the trash.\nThis will show trashed files in their original directory structure.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'formats', + Help: 'Deprecated: see export_formats', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 2, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'export_formats', + Help: 'Comma separated list of preferred formats for downloading Google docs.', + Provider: '', + Default: 'docx,xlsx,pptx,svg', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'import_formats', + Help: 'Comma separated list of preferred formats for uploading Google docs.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'allow_import_name_change', + Help: 'Allow the filetype to change when uploading Google docs (e.g. file.doc to file.docx). This will confuse sync and reupload every time.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'use_created_date', + Help: 'Use file created date instead of modified date.,\n\nUseful when downloading data and you want the creation date used in\nplace of the last modified date.\n\n**WARNING**: This flag may have some unexpected consequences.\n\nWhen uploading to your drive all files will be overwritten unless they\nhaven\'t been modified since their creation. And the inverse will occur\nwhile downloading. This side effect can be avoided by using the\n"--checksum" flag.\n\nThis feature was implemented to retain photos capture date as recorded\nby google photos. You will first need to check the "Create a Google\nPhotos folder" option in your google drive settings. You can then copy\nor move the photos locally and use the date the image was taken\n(created) set as the modification date.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'list_chunk', + Help: 'Size of listing chunk 100-1000. 0 to disable.', + Provider: '', + Default: 1000, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'impersonate', + Help: 'Impersonate this user when using a service account.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'alternate_export', + Help: "Use alternate export URLs for google documents export.,\n\nIf this option is set this instructs rclone to use an alternate set of\nexport URLs for drive documents. Users have reported that the\nofficial export URLs can't export large documents, whereas these\nunofficial ones can.\n\nSee rclone issue [#2243](https://github.com/ncw/rclone/issues/2243) for background,\n[this google drive issue](https://issuetracker.google.com/issues/36761333) and\n[this helpful post](https://www.labnol.org/internet/direct-links-for-google-drive/28356/).", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'upload_cutoff', + Help: 'Cutoff for switching to chunked upload', + Provider: '', + Default: 8388608, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_size', + Help: 'Upload chunk size. Must a power of 2 \u003e= 256k.\n\nMaking this larger will improve performance, but note that each chunk\nis buffered in memory one per transfer.\n\nReducing this will reduce memory usage but decrease performance.', + Provider: '', + Default: 8388608, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'acknowledge_abuse', + Help: 'Set to allow files which return cannotDownloadAbusiveFile to be downloaded.\n\nIf downloading a file returns the error "This file has been identified\nas malware or spam and cannot be downloaded" with the error code\n"cannotDownloadAbusiveFile" then supply this flag to rclone to\nindicate you acknowledge the risks of downloading the file and rclone\nwill download it anyway.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'keep_revision_forever', + Help: 'Keep new head revision of each file forever.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'v2_download_min_size', + Help: "If Object's are greater, use drive v2 API to download.", + Provider: '', + Default: -1, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'pacer_min_sleep', + Help: 'Minimum time to sleep between API calls.', + Provider: '', + Default: 100000000, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'pacer_burst', + Help: 'Number of API calls to allow without sleeping.', + Provider: '', + Default: 100, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'dropbox', + Description: 'Dropbox', + Prefix: 'dropbox', + Options: [ + { + Name: 'client_id', + Help: 'Dropbox App Client Id\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Dropbox App Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'chunk_size', + Help: 'Upload chunk size. (\u003c 150M).\n\nAny files larger than this will be uploaded in chunks of this size.\n\nNote that chunks are buffered in memory (one at a time) so rclone can\ndeal with retries. Setting this larger will increase the speed\nslightly (at most 10% for 128MB in tests) at the cost of using more\nmemory. It can be set smaller if you are tight on memory.', + Provider: '', + Default: 50331648, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'impersonate', + Help: 'Impersonate this user when using a business account.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'ftp', + Description: 'FTP Connection', + Prefix: 'ftp', + Options: [ + { + Name: 'host', + Help: 'FTP host to connect to', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'ftp.example.com', + Help: 'Connect to ftp.example.com', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'user', + Help: 'FTP username, leave blank for current username, negative0', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'port', + Help: 'FTP port, leave blank to use default (21)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'pass', + Help: 'FTP password', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'google cloud storage', + Description: 'Google Cloud Storage (this is not Google Drive)', + Prefix: 'gcs', + Options: [ + { + Name: 'client_id', + Help: 'Google Application Client Id\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Google Application Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'project_number', + Help: 'Project number.\nOptional - needed only for list/create/delete buckets - see your developer console.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'service_account_file', + Help: 'Service Account Credentials JSON file path\nLeave blank normally.\nNeeded only if you want use SA instead of interactive login.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'service_account_credentials', + Help: 'Service Account Credentials JSON blob\nLeave blank normally.\nNeeded only if you want use SA instead of interactive login.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 3, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'object_acl', + Help: 'Access Control List for new objects.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'authenticatedRead', + Help: 'Object owner gets OWNER access, and all Authenticated Users get READER access.', + Provider: '', + }, + { + Value: 'bucketOwnerFullControl', + Help: 'Object owner gets OWNER access, and project team owners get OWNER access.', + Provider: '', + }, + { + Value: 'bucketOwnerRead', + Help: 'Object owner gets OWNER access, and project team owners get READER access.', + Provider: '', + }, + { + Value: 'private', + Help: 'Object owner gets OWNER access [default if left blank].', + Provider: '', + }, + { + Value: 'projectPrivate', + Help: 'Object owner gets OWNER access, and project team members get access according to their roles.', + Provider: '', + }, + { + Value: 'publicRead', + Help: 'Object owner gets OWNER access, and all Users get READER access.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'bucket_acl', + Help: 'Access Control List for new buckets.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'authenticatedRead', + Help: 'Project team owners get OWNER access, and all Authenticated Users get READER access.', + Provider: '', + }, + { + Value: 'private', + Help: 'Project team owners get OWNER access [default if left blank].', + Provider: '', + }, + { + Value: 'projectPrivate', + Help: 'Project team members get access according to their roles.', + Provider: '', + }, + { + Value: 'publicRead', + Help: 'Project team owners get OWNER access, and all Users get READER access.', + Provider: '', + }, + { + Value: 'publicReadWrite', + Help: 'Project team owners get OWNER access, and all Users get WRITER access.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'location', + Help: 'Location for the newly created buckets.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'Empty for default location (US).', + Provider: '', + }, + { + Value: 'asia', + Help: 'Multi-regional location for Asia.', + Provider: '', + }, + { + Value: 'eu', + Help: 'Multi-regional location for Europe.', + Provider: '', + }, + { + Value: 'us', + Help: 'Multi-regional location for United States.', + Provider: '', + }, + { + Value: 'asia-east1', + Help: 'Taiwan.', + Provider: '', + }, + { + Value: 'asia-east2', + Help: 'Hong Kong.', + Provider: '', + }, + { + Value: 'asia-northeast1', + Help: 'Tokyo.', + Provider: '', + }, + { + Value: 'asia-south1', + Help: 'Mumbai.', + Provider: '', + }, + { + Value: 'asia-southeast1', + Help: 'Singapore.', + Provider: '', + }, + { + Value: 'australia-southeast1', + Help: 'Sydney.', + Provider: '', + }, + { + Value: 'europe-north1', + Help: 'Finland.', + Provider: '', + }, + { + Value: 'europe-west1', + Help: 'Belgium.', + Provider: '', + }, + { + Value: 'europe-west2', + Help: 'London.', + Provider: '', + }, + { + Value: 'europe-west3', + Help: 'Frankfurt.', + Provider: '', + }, + { + Value: 'europe-west4', + Help: 'Netherlands.', + Provider: '', + }, + { + Value: 'us-central1', + Help: 'Iowa.', + Provider: '', + }, + { + Value: 'us-east1', + Help: 'South Carolina.', + Provider: '', + }, + { + Value: 'us-east4', + Help: 'Northern Virginia.', + Provider: '', + }, + { + Value: 'us-west1', + Help: 'Oregon.', + Provider: '', + }, + { + Value: 'us-west2', + Help: 'California.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'storage_class', + Help: 'The storage class to use when storing objects in Google Cloud Storage.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'Default', + Provider: '', + }, + { + Value: 'MULTI_REGIONAL', + Help: 'Multi-regional storage class', + Provider: '', + }, + { + Value: 'REGIONAL', + Help: 'Regional storage class', + Provider: '', + }, + { + Value: 'NEARLINE', + Help: 'Nearline storage class', + Provider: '', + }, + { + Value: 'COLDLINE', + Help: 'Coldline storage class', + Provider: '', + }, + { + Value: 'DURABLE_REDUCED_AVAILABILITY', + Help: 'Durable reduced availability storage class', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'http', + Description: 'http Connection', + Prefix: 'http', + Options: [ + { + Name: 'url', + Help: 'URL of http host to connect to', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'https://example.com', + Help: 'Connect to example.com', + Provider: '', + }, + { + Value: 'https://user:pass@example.com', + Help: 'Connect to example.com using a username and password', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'swift', + Description: 'Openstack Swift (Rackspace Cloud Files, Memset Memstore, OVH)', + Prefix: 'swift', + Options: [ + { + Name: 'env_auth', + Help: 'Get swift credentials from environment variables in standard OpenStack form.', + Provider: '', + Default: false, + Value: null, + Examples: [ + { + Value: 'false', + Help: 'Enter swift credentials in the next step', + Provider: '', + }, + { + Value: 'true', + Help: 'Get swift credentials from environment vars. Leave other fields blank if using this.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'user', + Help: 'User name to log in (OS_USERNAME).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'key', + Help: 'API key or password (OS_PASSWORD).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'auth', + Help: 'Authentication URL for server (OS_AUTH_URL).', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'https://auth.api.rackspacecloud.com/v1.0', + Help: 'Rackspace US', + Provider: '', + }, + { + Value: 'https://lon.auth.api.rackspacecloud.com/v1.0', + Help: 'Rackspace UK', + Provider: '', + }, + { + Value: 'https://identity.api.rackspacecloud.com/v2.0', + Help: 'Rackspace v2', + Provider: '', + }, + { + Value: 'https://auth.storage.memset.com/v1.0', + Help: 'Memset Memstore UK', + Provider: '', + }, + { + Value: 'https://auth.storage.memset.com/v2.0', + Help: 'Memset Memstore UK v2', + Provider: '', + }, + { + Value: 'https://auth.cloud.ovh.net/v2.0', + Help: 'OVH', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'user_id', + Help: 'User ID to log in - optional - most swift systems use user and leave this blank (v3 auth) (OS_USER_ID).', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'domain', + Help: 'User domain - optional (v3 auth) (OS_USER_DOMAIN_NAME)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'tenant', + Help: 'Tenant name - optional for v1 auth, this or tenant_id required otherwise (OS_TENANT_NAME or OS_PROJECT_NAME)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'tenant_id', + Help: 'Tenant ID - optional for v1 auth, this or tenant required otherwise (OS_TENANT_ID)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'tenant_domain', + Help: 'Tenant domain - optional (v3 auth) (OS_PROJECT_DOMAIN_NAME)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'region', + Help: 'Region name - optional (OS_REGION_NAME)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'storage_url', + Help: 'Storage URL - optional (OS_STORAGE_URL)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'auth_token', + Help: 'Auth Token from alternate authentication - optional (OS_AUTH_TOKEN)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'application_credential_id', + Help: 'Application Credential ID (OS_APPLICATION_CREDENTIAL_ID)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'application_credential_name', + Help: 'Application Credential Name (OS_APPLICATION_CREDENTIAL_NAME)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'application_credential_secret', + Help: 'Application Credential Secret (OS_APPLICATION_CREDENTIAL_SECRET)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'auth_version', + Help: 'AuthVersion - optional - set to (1,2,3) if your auth URL has no version (ST_AUTH_VERSION)', + Provider: '', + Default: 0, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint_type', + Help: 'Endpoint type to choose from the service catalogue (OS_ENDPOINT_TYPE)', + Provider: '', + Default: 'public', + Value: null, + Examples: [ + { + Value: 'public', + Help: 'Public (default, choose this if not sure)', + Provider: '', + }, + { + Value: 'internal', + Help: 'Internal (use internal service net)', + Provider: '', + }, + { + Value: 'admin', + Help: 'Admin', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'storage_policy', + Help: 'The storage policy to use when creating a new container\n\nThis applies the specified storage policy when creating a new\ncontainer. The policy cannot be changed afterwards. The allowed\nconfiguration values and their meaning depend on your Swift storage\nprovider.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'Default', + Provider: '', + }, + { + Value: 'pcs', + Help: 'OVH Public Cloud Storage', + Provider: '', + }, + { + Value: 'pca', + Help: 'OVH Public Cloud Archive', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'chunk_size', + Help: 'Above this size files will be chunked into a _segments container.\n\nAbove this size files will be chunked into a _segments container. The\ndefault for this is 5GB which is its maximum value.', + Provider: '', + Default: 5368709120, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'no_chunk', + Help: "Don't chunk files during streaming upload.\n\nWhen doing streaming uploads (eg using rcat or mount) setting this\nflag will cause the swift backend to not upload chunked files.\n\nThis will limit the maximum upload size to 5GB. However non chunked\nfiles are easier to deal with and have an MD5SUM.\n\nRclone will still chunk files bigger than chunk_size when doing normal\ncopy operations.", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'hubic', + Description: 'Hubic', + Prefix: 'hubic', + Options: [ + { + Name: 'client_id', + Help: 'Hubic Client Id\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Hubic Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'chunk_size', + Help: 'Above this size files will be chunked into a _segments container.\n\nAbove this size files will be chunked into a _segments container. The\ndefault for this is 5GB which is its maximum value.', + Provider: '', + Default: 5368709120, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'no_chunk', + Help: "Don't chunk files during streaming upload.\n\nWhen doing streaming uploads (eg using rcat or mount) setting this\nflag will cause the swift backend to not upload chunked files.\n\nThis will limit the maximum upload size to 5GB. However non chunked\nfiles are easier to deal with and have an MD5SUM.\n\nRclone will still chunk files bigger than chunk_size when doing normal\ncopy operations.", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'jottacloud', + Description: 'JottaCloud', + Prefix: 'jottacloud', + Options: [ + { + Name: 'user', + Help: 'User Name:', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'mountpoint', + Help: 'The mountpoint to use.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'Sync', + Help: 'Will be synced by the official client.', + Provider: '', + }, + { + Value: 'Archive', + Help: 'Archive', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'md5_memory_limit', + Help: 'Files bigger than this will be cached on disk to calculate the MD5 if required.', + Provider: '', + Default: 10485760, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'hard_delete', + Help: 'Delete files permanently rather than putting them into the trash.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'unlink', + Help: 'Remove existing public link to file/folder with link command rather than creating.\nDefault is false, meaning link command will create or retrieve public link.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'upload_resume_limit', + Help: "Files bigger than this can be resumed if the upload fail's.", + Provider: '', + Default: 10485760, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'local', + Description: 'Local Disk', + Prefix: 'local', + Options: [ + { + Name: 'nounc', + Help: 'Disable UNC (long path names) conversion on Windows', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'true', + Help: 'Disables long file names', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'copy_links', + Help: 'Follow symlinks and copy the pointed to item.', + Provider: '', + Default: false, + Value: null, + ShortOpt: 'L', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: true, + Advanced: true, + }, + { + Name: 'links', + Help: "Translate symlinks to/from regular files with a '.rclonelink' extension", + Provider: '', + Default: false, + Value: null, + ShortOpt: 'l', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: true, + Advanced: true, + }, + { + Name: 'skip_links', + Help: "Don't warn about skipped symlinks.\nThis flag disables warning messages on skipped symlinks or junction\npoints, as you explicitly acknowledge that they should be skipped.", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: true, + Advanced: true, + }, + { + Name: 'no_unicode_normalization', + Help: "Don't apply unicode normalization to paths and filenames (Deprecated)\n\nThis flag is deprecated now. Rclone no longer normalizes unicode file\nnames, but it compares them with unicode normalization in the sync\nroutine instead.", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'no_check_updated', + Help: 'Don\'t check to see if the files change during upload\n\nNormally rclone checks the size and modification time of files as they\nare being uploaded and aborts with a message which starts "can\'t copy\n- source file is being updated" if the file changes during upload.\n\nHowever on some file systems this modification time check may fail (eg\n[Glusterfs #2206](https://github.com/ncw/rclone/issues/2206)) so this\ncheck can be disabled with this flag.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'one_file_system', + Help: "Don't cross filesystem boundaries (unix/macOS only).", + Provider: '', + Default: false, + Value: null, + ShortOpt: 'x', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: true, + Advanced: true, + }, + ], + }, + { + Name: 'mega', + Description: 'Mega', + Prefix: 'mega', + Options: [ + { + Name: 'user', + Help: 'User name', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'pass', + Help: 'Password.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'debug', + Help: 'Output more debug from Mega.\n\nIf this flag is set (along with -vv) it will print further debugging\ninformation from the mega backend.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'hard_delete', + Help: 'Delete files permanently rather than putting them into the trash.\n\nNormally the mega backend will put all deletions into the trash rather\nthan permanently deleting them. If you specify this then rclone will\npermanently delete objects instead.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'onedrive', + Description: 'Microsoft OneDrive', + Prefix: 'onedrive', + Options: [ + { + Name: 'client_id', + Help: 'Microsoft App Client Id\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Microsoft App Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'chunk_size', + Help: 'Chunk size to upload files with - must be multiple of 320k.\n\nAbove this size files will be chunked - must be multiple of 320k. Note\nthat the chunks will be buffered into memory.', + Provider: '', + Default: 10485760, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'drive_id', + Help: 'The ID of the drive to use', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'drive_type', + Help: 'The type of the drive ( personal | business | documentLibrary )', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'expose_onenote_files', + Help: 'Set to make OneNote files show up in directory listings.\n\nBy default rclone will hide OneNote files in directory listings because\noperations like "Open" and "Update" won\'t work on them. But this\nbehaviour may also prevent you from deleting them. If you want to\ndelete OneNote files or otherwise want them to show up in directory\nlisting, set this option.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'opendrive', + Description: 'OpenDrive', + Prefix: 'opendrive', + Options: [ + { + Name: 'username', + Help: 'Username', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'password', + Help: 'Password.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'pcloud', + Description: 'Pcloud', + Prefix: 'pcloud', + Options: [ + { + Name: 'client_id', + Help: 'Pcloud App Client Id\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Pcloud App Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'qingstor', + Description: 'QingCloud Object Storage', + Prefix: 'qingstor', + Options: [ + { + Name: 'env_auth', + Help: 'Get QingStor credentials from runtime. Only applies if access_key_id and secret_access_key is blank.', + Provider: '', + Default: false, + Value: null, + Examples: [ + { + Value: 'false', + Help: 'Enter QingStor credentials in the next step', + Provider: '', + }, + { + Value: 'true', + Help: 'Get QingStor credentials from the environment (env vars or IAM)', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'access_key_id', + Help: 'QingStor Access Key ID\nLeave blank for anonymous access or runtime credentials.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'secret_access_key', + Help: 'QingStor Secret Access Key (password)\nLeave blank for anonymous access or runtime credentials.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint', + Help: 'Enter a endpoint URL to connection QingStor API.\nLeave blank will use the default value "https://qingstor.com:443"', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'zone', + Help: 'Zone to connect to.\nDefault is "pek3a".', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'pek3a', + Help: 'The Beijing (China) Three Zone\nNeeds location constraint pek3a.', + Provider: '', + }, + { + Value: 'sh1a', + Help: 'The Shanghai (China) First Zone\nNeeds location constraint sh1a.', + Provider: '', + }, + { + Value: 'gd2a', + Help: 'The Guangdong (China) Second Zone\nNeeds location constraint gd2a.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'connection_retries', + Help: 'Number of connection retries.', + Provider: '', + Default: 3, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'upload_cutoff', + Help: 'Cutoff for switching to chunked upload\n\nAny files larger than this will be uploaded in chunks of chunk_size.\nThe minimum is 0 and the maximum is 5GB.', + Provider: '', + Default: 209715200, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_size', + Help: 'Chunk size to use for uploading.\n\nWhen uploading files larger than upload_cutoff they will be uploaded\nas multipart uploads using this chunk size.\n\nNote that "--qingstor-upload-concurrency" chunks of this size are buffered\nin memory per transfer.\n\nIf you are transferring large files over high speed links and you have\nenough memory, then increasing this will speed up the transfers.', + Provider: '', + Default: 4194304, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'upload_concurrency', + Help: 'Concurrency for multipart uploads.\n\nThis is the number of chunks of the same file that are uploaded\nconcurrently.\n\nNB if you set this to \u003e 1 then the checksums of multpart uploads\nbecome corrupted (the uploads themselves are not corrupted though).\n\nIf you are uploading small numbers of large file over high speed link\nand these uploads do not fully utilize your bandwidth, then increasing\nthis may help to speed up the transfers.', + Provider: '', + Default: 1, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 's3', + Description: + 'Amazon S3 Compliant Storage Provider (AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Minio, etc)', + Prefix: 's3', + Options: [ + { + Name: 'provider', + Help: 'Choose your S3 provider.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'AWS', + Help: 'Amazon Web Services (AWS) S3', + Provider: '', + }, + { + Value: 'Alibaba', + Help: 'Alibaba Cloud Object Storage System (OSS) formerly Aliyun', + Provider: '', + }, + { + Value: 'Ceph', + Help: 'Ceph Object Storage', + Provider: '', + }, + { + Value: 'DigitalOcean', + Help: 'Digital Ocean Spaces', + Provider: '', + }, + { + Value: 'Dreamhost', + Help: 'Dreamhost DreamObjects', + Provider: '', + }, + { + Value: 'IBMCOS', + Help: 'IBM COS S3', + Provider: '', + }, + { + Value: 'Minio', + Help: 'Minio Object Storage', + Provider: '', + }, + { + Value: 'Netease', + Help: 'Netease Object Storage (NOS)', + Provider: '', + }, + { + Value: 'Wasabi', + Help: 'Wasabi Object Storage', + Provider: '', + }, + { + Value: 'Other', + Help: 'Any other S3 compatible provider', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'env_auth', + Help: 'Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).\nOnly applies if access_key_id and secret_access_key is blank.', + Provider: '', + Default: false, + Value: null, + Examples: [ + { + Value: 'false', + Help: 'Enter AWS credentials in the next step', + Provider: '', + }, + { + Value: 'true', + Help: 'Get AWS credentials from the environment (env vars or IAM)', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'access_key_id', + Help: 'AWS Access Key ID.\nLeave blank for anonymous access or runtime credentials.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'secret_access_key', + Help: 'AWS Secret Access Key (password)\nLeave blank for anonymous access or runtime credentials.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'region', + Help: 'Region to connect to.', + Provider: 'AWS', + Default: '', + Value: null, + Examples: [ + { + Value: 'us-east-1', + Help: 'The default endpoint - a good choice if you are unsure.\nUS Region, Northern Virginia or Pacific Northwest.\nLeave location constraint empty.', + Provider: '', + }, + { + Value: 'us-east-2', + Help: 'US East (Ohio) Region\nNeeds location constraint us-east-2.', + Provider: '', + }, + { + Value: 'us-west-2', + Help: 'US West (Oregon) Region\nNeeds location constraint us-west-2.', + Provider: '', + }, + { + Value: 'us-west-1', + Help: 'US West (Northern California) Region\nNeeds location constraint us-west-1.', + Provider: '', + }, + { + Value: 'ca-central-1', + Help: 'Canada (Central) Region\nNeeds location constraint ca-central-1.', + Provider: '', + }, + { + Value: 'eu-west-1', + Help: 'EU (Ireland) Region\nNeeds location constraint EU or eu-west-1.', + Provider: '', + }, + { + Value: 'eu-west-2', + Help: 'EU (London) Region\nNeeds location constraint eu-west-2.', + Provider: '', + }, + { + Value: 'eu-north-1', + Help: 'EU (Stockholm) Region\nNeeds location constraint eu-north-1.', + Provider: '', + }, + { + Value: 'eu-central-1', + Help: 'EU (Frankfurt) Region\nNeeds location constraint eu-central-1.', + Provider: '', + }, + { + Value: 'ap-southeast-1', + Help: 'Asia Pacific (Singapore) Region\nNeeds location constraint ap-southeast-1.', + Provider: '', + }, + { + Value: 'ap-southeast-2', + Help: 'Asia Pacific (Sydney) Region\nNeeds location constraint ap-southeast-2.', + Provider: '', + }, + { + Value: 'ap-northeast-1', + Help: 'Asia Pacific (Tokyo) Region\nNeeds location constraint ap-northeast-1.', + Provider: '', + }, + { + Value: 'ap-northeast-2', + Help: 'Asia Pacific (Seoul)\nNeeds location constraint ap-northeast-2.', + Provider: '', + }, + { + Value: 'ap-south-1', + Help: 'Asia Pacific (Mumbai)\nNeeds location constraint ap-south-1.', + Provider: '', + }, + { + Value: 'sa-east-1', + Help: 'South America (Sao Paulo) Region\nNeeds location constraint sa-east-1.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'region', + Help: "Region to connect to.\nLeave blank if you are using an S3 clone and you don't have a region.", + Provider: '!AWS,Alibaba', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'Use this if unsure. Will use v4 signatures and an empty region.', + Provider: '', + }, + { + Value: 'other-v2-signature', + Help: "Use this only if v4 signatures don't work, eg pre Jewel/v10 CEPH.", + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint', + Help: 'Endpoint for S3 API.\nLeave blank if using AWS to use the default endpoint for the region.', + Provider: 'AWS', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint', + Help: 'Endpoint for IBM COS S3 API.\nSpecify if using an IBM COS On Premise.', + Provider: 'IBMCOS', + Default: '', + Value: null, + Examples: [ + { + Value: 's3-api.us-geo.objectstorage.softlayer.net', + Help: 'US Cross Region Endpoint', + Provider: '', + }, + { + Value: 's3-api.dal.us-geo.objectstorage.softlayer.net', + Help: 'US Cross Region Dallas Endpoint', + Provider: '', + }, + { + Value: 's3-api.wdc-us-geo.objectstorage.softlayer.net', + Help: 'US Cross Region Washington DC Endpoint', + Provider: '', + }, + { + Value: 's3-api.sjc-us-geo.objectstorage.softlayer.net', + Help: 'US Cross Region San Jose Endpoint', + Provider: '', + }, + { + Value: 's3-api.us-geo.objectstorage.service.networklayer.com', + Help: 'US Cross Region Private Endpoint', + Provider: '', + }, + { + Value: 's3-api.dal-us-geo.objectstorage.service.networklayer.com', + Help: 'US Cross Region Dallas Private Endpoint', + Provider: '', + }, + { + Value: 's3-api.wdc-us-geo.objectstorage.service.networklayer.com', + Help: 'US Cross Region Washington DC Private Endpoint', + Provider: '', + }, + { + Value: 's3-api.sjc-us-geo.objectstorage.service.networklayer.com', + Help: 'US Cross Region San Jose Private Endpoint', + Provider: '', + }, + { + Value: 's3.us-east.objectstorage.softlayer.net', + Help: 'US Region East Endpoint', + Provider: '', + }, + { + Value: 's3.us-east.objectstorage.service.networklayer.com', + Help: 'US Region East Private Endpoint', + Provider: '', + }, + { + Value: 's3.us-south.objectstorage.softlayer.net', + Help: 'US Region South Endpoint', + Provider: '', + }, + { + Value: 's3.us-south.objectstorage.service.networklayer.com', + Help: 'US Region South Private Endpoint', + Provider: '', + }, + { + Value: 's3.eu-geo.objectstorage.softlayer.net', + Help: 'EU Cross Region Endpoint', + Provider: '', + }, + { + Value: 's3.fra-eu-geo.objectstorage.softlayer.net', + Help: 'EU Cross Region Frankfurt Endpoint', + Provider: '', + }, + { + Value: 's3.mil-eu-geo.objectstorage.softlayer.net', + Help: 'EU Cross Region Milan Endpoint', + Provider: '', + }, + { + Value: 's3.ams-eu-geo.objectstorage.softlayer.net', + Help: 'EU Cross Region Amsterdam Endpoint', + Provider: '', + }, + { + Value: 's3.eu-geo.objectstorage.service.networklayer.com', + Help: 'EU Cross Region Private Endpoint', + Provider: '', + }, + { + Value: 's3.fra-eu-geo.objectstorage.service.networklayer.com', + Help: 'EU Cross Region Frankfurt Private Endpoint', + Provider: '', + }, + { + Value: 's3.mil-eu-geo.objectstorage.service.networklayer.com', + Help: 'EU Cross Region Milan Private Endpoint', + Provider: '', + }, + { + Value: 's3.ams-eu-geo.objectstorage.service.networklayer.com', + Help: 'EU Cross Region Amsterdam Private Endpoint', + Provider: '', + }, + { + Value: 's3.eu-gb.objectstorage.softlayer.net', + Help: 'Great Britain Endpoint', + Provider: '', + }, + { + Value: 's3.eu-gb.objectstorage.service.networklayer.com', + Help: 'Great Britain Private Endpoint', + Provider: '', + }, + { + Value: 's3.ap-geo.objectstorage.softlayer.net', + Help: 'APAC Cross Regional Endpoint', + Provider: '', + }, + { + Value: 's3.tok-ap-geo.objectstorage.softlayer.net', + Help: 'APAC Cross Regional Tokyo Endpoint', + Provider: '', + }, + { + Value: 's3.hkg-ap-geo.objectstorage.softlayer.net', + Help: 'APAC Cross Regional HongKong Endpoint', + Provider: '', + }, + { + Value: 's3.seo-ap-geo.objectstorage.softlayer.net', + Help: 'APAC Cross Regional Seoul Endpoint', + Provider: '', + }, + { + Value: 's3.ap-geo.objectstorage.service.networklayer.com', + Help: 'APAC Cross Regional Private Endpoint', + Provider: '', + }, + { + Value: 's3.tok-ap-geo.objectstorage.service.networklayer.com', + Help: 'APAC Cross Regional Tokyo Private Endpoint', + Provider: '', + }, + { + Value: 's3.hkg-ap-geo.objectstorage.service.networklayer.com', + Help: 'APAC Cross Regional HongKong Private Endpoint', + Provider: '', + }, + { + Value: 's3.seo-ap-geo.objectstorage.service.networklayer.com', + Help: 'APAC Cross Regional Seoul Private Endpoint', + Provider: '', + }, + { + Value: 's3.mel01.objectstorage.softlayer.net', + Help: 'Melbourne Single Site Endpoint', + Provider: '', + }, + { + Value: 's3.mel01.objectstorage.service.networklayer.com', + Help: 'Melbourne Single Site Private Endpoint', + Provider: '', + }, + { + Value: 's3.tor01.objectstorage.softlayer.net', + Help: 'Toronto Single Site Endpoint', + Provider: '', + }, + { + Value: 's3.tor01.objectstorage.service.networklayer.com', + Help: 'Toronto Single Site Private Endpoint', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint', + Help: 'Endpoint for OSS API.', + Provider: 'Alibaba', + Default: '', + Value: null, + Examples: [ + { + Value: 'oss-cn-hangzhou.aliyuncs.com', + Help: 'East China 1 (Hangzhou)', + Provider: '', + }, + { + Value: 'oss-cn-shanghai.aliyuncs.com', + Help: 'East China 2 (Shanghai)', + Provider: '', + }, + { + Value: 'oss-cn-qingdao.aliyuncs.com', + Help: 'North China 1 (Qingdao)', + Provider: '', + }, + { + Value: 'oss-cn-beijing.aliyuncs.com', + Help: 'North China 2 (Beijing)', + Provider: '', + }, + { + Value: 'oss-cn-zhangjiakou.aliyuncs.com', + Help: 'North China 3 (Zhangjiakou)', + Provider: '', + }, + { + Value: 'oss-cn-huhehaote.aliyuncs.com', + Help: 'North China 5 (Huhehaote)', + Provider: '', + }, + { + Value: 'oss-cn-shenzhen.aliyuncs.com', + Help: 'South China 1 (Shenzhen)', + Provider: '', + }, + { + Value: 'oss-cn-hongkong.aliyuncs.com', + Help: 'Hong Kong (Hong Kong)', + Provider: '', + }, + { + Value: 'oss-us-west-1.aliyuncs.com', + Help: 'US West 1 (Silicon Valley)', + Provider: '', + }, + { + Value: 'oss-us-east-1.aliyuncs.com', + Help: 'US East 1 (Virginia)', + Provider: '', + }, + { + Value: 'oss-ap-southeast-1.aliyuncs.com', + Help: 'Southeast Asia Southeast 1 (Singapore)', + Provider: '', + }, + { + Value: 'oss-ap-southeast-2.aliyuncs.com', + Help: 'Asia Pacific Southeast 2 (Sydney)', + Provider: '', + }, + { + Value: 'oss-ap-southeast-3.aliyuncs.com', + Help: 'Southeast Asia Southeast 3 (Kuala Lumpur)', + Provider: '', + }, + { + Value: 'oss-ap-southeast-5.aliyuncs.com', + Help: 'Asia Pacific Southeast 5 (Jakarta)', + Provider: '', + }, + { + Value: 'oss-ap-northeast-1.aliyuncs.com', + Help: 'Asia Pacific Northeast 1 (Japan)', + Provider: '', + }, + { + Value: 'oss-ap-south-1.aliyuncs.com', + Help: 'Asia Pacific South 1 (Mumbai)', + Provider: '', + }, + { + Value: 'oss-eu-central-1.aliyuncs.com', + Help: 'Central Europe 1 (Frankfurt)', + Provider: '', + }, + { + Value: 'oss-eu-west-1.aliyuncs.com', + Help: 'West Europe (London)', + Provider: '', + }, + { + Value: 'oss-me-east-1.aliyuncs.com', + Help: 'Middle East 1 (Dubai)', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'endpoint', + Help: 'Endpoint for S3 API.\nRequired when using an S3 clone.', + Provider: '!AWS,IBMCOS,Alibaba', + Default: '', + Value: null, + Examples: [ + { + Value: 'objects-us-west-1.dream.io', + Help: 'Dream Objects endpoint', + Provider: 'Dreamhost', + }, + { + Value: 'nyc3.digitaloceanspaces.com', + Help: 'Digital Ocean Spaces New York 3', + Provider: 'DigitalOcean', + }, + { + Value: 'ams3.digitaloceanspaces.com', + Help: 'Digital Ocean Spaces Amsterdam 3', + Provider: 'DigitalOcean', + }, + { + Value: 'sgp1.digitaloceanspaces.com', + Help: 'Digital Ocean Spaces Singapore 1', + Provider: 'DigitalOcean', + }, + { + Value: 's3.wasabisys.com', + Help: 'Wasabi US East endpoint', + Provider: 'Wasabi', + }, + { + Value: 's3.us-west-1.wasabisys.com', + Help: 'Wasabi US West endpoint', + Provider: 'Wasabi', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'location_constraint', + Help: 'Location constraint - must be set to match the Region.\nUsed when creating buckets only.', + Provider: 'AWS', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'Empty for US Region, Northern Virginia or Pacific Northwest.', + Provider: '', + }, + { + Value: 'us-east-2', + Help: 'US East (Ohio) Region.', + Provider: '', + }, + { + Value: 'us-west-2', + Help: 'US West (Oregon) Region.', + Provider: '', + }, + { + Value: 'us-west-1', + Help: 'US West (Northern California) Region.', + Provider: '', + }, + { + Value: 'ca-central-1', + Help: 'Canada (Central) Region.', + Provider: '', + }, + { + Value: 'eu-west-1', + Help: 'EU (Ireland) Region.', + Provider: '', + }, + { + Value: 'eu-west-2', + Help: 'EU (London) Region.', + Provider: '', + }, + { + Value: 'eu-north-1', + Help: 'EU (Stockholm) Region.', + Provider: '', + }, + { + Value: 'EU', + Help: 'EU Region.', + Provider: '', + }, + { + Value: 'ap-southeast-1', + Help: 'Asia Pacific (Singapore) Region.', + Provider: '', + }, + { + Value: 'ap-southeast-2', + Help: 'Asia Pacific (Sydney) Region.', + Provider: '', + }, + { + Value: 'ap-northeast-1', + Help: 'Asia Pacific (Tokyo) Region.', + Provider: '', + }, + { + Value: 'ap-northeast-2', + Help: 'Asia Pacific (Seoul)', + Provider: '', + }, + { + Value: 'ap-south-1', + Help: 'Asia Pacific (Mumbai)', + Provider: '', + }, + { + Value: 'sa-east-1', + Help: 'South America (Sao Paulo) Region.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'location_constraint', + Help: 'Location constraint - must match endpoint when using IBM Cloud Public.\nFor on-prem COS, do not make a selection from this list, hit enter', + Provider: 'IBMCOS', + Default: '', + Value: null, + Examples: [ + { + Value: 'us-standard', + Help: 'US Cross Region Standard', + Provider: '', + }, + { + Value: 'us-vault', + Help: 'US Cross Region Vault', + Provider: '', + }, + { + Value: 'us-cold', + Help: 'US Cross Region Cold', + Provider: '', + }, + { + Value: 'us-flex', + Help: 'US Cross Region Flex', + Provider: '', + }, + { + Value: 'us-east-standard', + Help: 'US East Region Standard', + Provider: '', + }, + { + Value: 'us-east-vault', + Help: 'US East Region Vault', + Provider: '', + }, + { + Value: 'us-east-cold', + Help: 'US East Region Cold', + Provider: '', + }, + { + Value: 'us-east-flex', + Help: 'US East Region Flex', + Provider: '', + }, + { + Value: 'us-south-standard', + Help: 'US South Region Standard', + Provider: '', + }, + { + Value: 'us-south-vault', + Help: 'US South Region Vault', + Provider: '', + }, + { + Value: 'us-south-cold', + Help: 'US South Region Cold', + Provider: '', + }, + { + Value: 'us-south-flex', + Help: 'US South Region Flex', + Provider: '', + }, + { + Value: 'eu-standard', + Help: 'EU Cross Region Standard', + Provider: '', + }, + { + Value: 'eu-vault', + Help: 'EU Cross Region Vault', + Provider: '', + }, + { + Value: 'eu-cold', + Help: 'EU Cross Region Cold', + Provider: '', + }, + { + Value: 'eu-flex', + Help: 'EU Cross Region Flex', + Provider: '', + }, + { + Value: 'eu-gb-standard', + Help: 'Great Britain Standard', + Provider: '', + }, + { + Value: 'eu-gb-vault', + Help: 'Great Britain Vault', + Provider: '', + }, + { + Value: 'eu-gb-cold', + Help: 'Great Britain Cold', + Provider: '', + }, + { + Value: 'eu-gb-flex', + Help: 'Great Britain Flex', + Provider: '', + }, + { + Value: 'ap-standard', + Help: 'APAC Standard', + Provider: '', + }, + { + Value: 'ap-vault', + Help: 'APAC Vault', + Provider: '', + }, + { + Value: 'ap-cold', + Help: 'APAC Cold', + Provider: '', + }, + { + Value: 'ap-flex', + Help: 'APAC Flex', + Provider: '', + }, + { + Value: 'mel01-standard', + Help: 'Melbourne Standard', + Provider: '', + }, + { + Value: 'mel01-vault', + Help: 'Melbourne Vault', + Provider: '', + }, + { + Value: 'mel01-cold', + Help: 'Melbourne Cold', + Provider: '', + }, + { + Value: 'mel01-flex', + Help: 'Melbourne Flex', + Provider: '', + }, + { + Value: 'tor01-standard', + Help: 'Toronto Standard', + Provider: '', + }, + { + Value: 'tor01-vault', + Help: 'Toronto Vault', + Provider: '', + }, + { + Value: 'tor01-cold', + Help: 'Toronto Cold', + Provider: '', + }, + { + Value: 'tor01-flex', + Help: 'Toronto Flex', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'location_constraint', + Help: 'Location constraint - must be set to match the Region.\nLeave blank if not sure. Used when creating buckets only.', + Provider: '!AWS,IBMCOS,Alibaba', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'acl', + Help: "Canned ACL used when creating buckets and storing or copying objects.\n\nThis ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.\n\nFor more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl\n\nNote that this ACL is applied when server side copying objects as S3\ndoesn't copy the ACL from the source but rather writes a fresh one.", + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'private', + Help: 'Owner gets FULL_CONTROL. No one else has access rights (default).', + Provider: '!IBMCOS', + }, + { + Value: 'public-read', + Help: 'Owner gets FULL_CONTROL. The AllUsers group gets READ access.', + Provider: '!IBMCOS', + }, + { + Value: 'public-read-write', + Help: 'Owner gets FULL_CONTROL. The AllUsers group gets READ and WRITE access.\nGranting this on a bucket is generally not recommended.', + Provider: '!IBMCOS', + }, + { + Value: 'authenticated-read', + Help: 'Owner gets FULL_CONTROL. The AuthenticatedUsers group gets READ access.', + Provider: '!IBMCOS', + }, + { + Value: 'bucket-owner-read', + Help: 'Object owner gets FULL_CONTROL. Bucket owner gets READ access.\nIf you specify this canned ACL when creating a bucket, Amazon S3 ignores it.', + Provider: '!IBMCOS', + }, + { + Value: 'bucket-owner-full-control', + Help: 'Both the object owner and the bucket owner get FULL_CONTROL over the object.\nIf you specify this canned ACL when creating a bucket, Amazon S3 ignores it.', + Provider: '!IBMCOS', + }, + { + Value: 'private', + Help: 'Owner gets FULL_CONTROL. No one else has access rights (default). This acl is available on IBM Cloud (Infra), IBM Cloud (Storage), On-Premise COS', + Provider: 'IBMCOS', + }, + { + Value: 'public-read', + Help: 'Owner gets FULL_CONTROL. The AllUsers group gets READ access. This acl is available on IBM Cloud (Infra), IBM Cloud (Storage), On-Premise IBM COS', + Provider: 'IBMCOS', + }, + { + Value: 'public-read-write', + Help: 'Owner gets FULL_CONTROL. The AllUsers group gets READ and WRITE access. This acl is available on IBM Cloud (Infra), On-Premise IBM COS', + Provider: 'IBMCOS', + }, + { + Value: 'authenticated-read', + Help: 'Owner gets FULL_CONTROL. The AuthenticatedUsers group gets READ access. Not supported on Buckets. This acl is available on IBM Cloud (Infra) and On-Premise IBM COS', + Provider: 'IBMCOS', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'bucket_acl', + Help: 'Canned ACL used when creating buckets.\n\nFor more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl\n\nNote that this ACL is applied when only when creating buckets. If it\nisn\'t set then "acl" is used instead.', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'private', + Help: 'Owner gets FULL_CONTROL. No one else has access rights (default).', + Provider: '', + }, + { + Value: 'public-read', + Help: 'Owner gets FULL_CONTROL. The AllUsers group gets READ access.', + Provider: '', + }, + { + Value: 'public-read-write', + Help: 'Owner gets FULL_CONTROL. The AllUsers group gets READ and WRITE access.\nGranting this on a bucket is generally not recommended.', + Provider: '', + }, + { + Value: 'authenticated-read', + Help: 'Owner gets FULL_CONTROL. The AuthenticatedUsers group gets READ access.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'server_side_encryption', + Help: 'The server-side encryption algorithm used when storing this object in S3.', + Provider: 'AWS', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'None', + Provider: '', + }, + { + Value: 'AES256', + Help: 'AES256', + Provider: '', + }, + { + Value: 'aws:kms', + Help: 'aws:kms', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'sse_kms_key_id', + Help: 'If using KMS ID you must provide the ARN of Key.', + Provider: 'AWS', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'None', + Provider: '', + }, + { + Value: 'arn:aws:kms:us-east-1:*', + Help: 'arn:aws:kms:*', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'storage_class', + Help: 'The storage class to use when storing new objects in S3.', + Provider: 'AWS', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'Default', + Provider: '', + }, + { + Value: 'STANDARD', + Help: 'Standard storage class', + Provider: '', + }, + { + Value: 'REDUCED_REDUNDANCY', + Help: 'Reduced redundancy storage class', + Provider: '', + }, + { + Value: 'STANDARD_IA', + Help: 'Standard Infrequent Access storage class', + Provider: '', + }, + { + Value: 'ONEZONE_IA', + Help: 'One Zone Infrequent Access storage class', + Provider: '', + }, + { + Value: 'GLACIER', + Help: 'Glacier storage class', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'storage_class', + Help: 'The storage class to use when storing new objects in OSS.', + Provider: 'Alibaba', + Default: '', + Value: null, + Examples: [ + { + Value: '', + Help: 'Default', + Provider: '', + }, + { + Value: 'STANDARD', + Help: 'Standard storage class', + Provider: '', + }, + { + Value: 'GLACIER', + Help: 'Archive storage mode.', + Provider: '', + }, + { + Value: 'STANDARD_IA', + Help: 'Infrequent access storage mode.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'upload_cutoff', + Help: 'Cutoff for switching to chunked upload\n\nAny files larger than this will be uploaded in chunks of chunk_size.\nThe minimum is 0 and the maximum is 5GB.', + Provider: '', + Default: 209715200, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'chunk_size', + Help: 'Chunk size to use for uploading.\n\nWhen uploading files larger than upload_cutoff they will be uploaded\nas multipart uploads using this chunk size.\n\nNote that "--s3-upload-concurrency" chunks of this size are buffered\nin memory per transfer.\n\nIf you are transferring large files over high speed links and you have\nenough memory, then increasing this will speed up the transfers.', + Provider: '', + Default: 5242880, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'disable_checksum', + Help: "Don't store MD5 checksum with object metadata", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'session_token', + Help: 'An AWS session token', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'upload_concurrency', + Help: 'Concurrency for multipart uploads.\n\nThis is the number of chunks of the same file that are uploaded\nconcurrently.\n\nIf you are uploading small numbers of large file over high speed link\nand these uploads do not fully utilize your bandwidth, then increasing\nthis may help to speed up the transfers.', + Provider: '', + Default: 4, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'force_path_style', + Help: 'If true use path style access if false use virtual hosted style.\n\nIf this is true (the default) then rclone will use path style access,\nif false then rclone will use virtual path style. See [the AWS S3\ndocs](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html#access-bucket-intro)\nfor more info.\n\nSome providers (eg Aliyun OSS or Netease COS) require this set to false.', + Provider: '', + Default: true, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'v2_auth', + Help: "If true use v2 authentication.\n\nIf this is false (the default) then rclone will use v4 authentication.\nIf it is set then rclone will use v2 authentication.\n\nUse this only if v4 signatures don't work, eg pre Jewel/v10 CEPH.", + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'sftp', + Description: 'SSH/SFTP Connection', + Prefix: 'sftp', + Options: [ + { + Name: 'host', + Help: 'SSH host to connect to', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'example.com', + Help: 'Connect to example.com', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'user', + Help: 'SSH username, leave blank for current username, negative0', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'port', + Help: 'SSH port, leave blank to use default (22)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'pass', + Help: 'SSH password, leave blank to use ssh-agent.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'key_file', + Help: 'Path to PEM-encoded private key file, leave blank or set key-use-agent to use ssh-agent.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'key_file_pass', + Help: "The passphrase to decrypt the PEM-encoded private key file.\n\nOnly PEM encrypted key files (old OpenSSH format) are supported. Encrypted keys\nin the new OpenSSH format can't be used.", + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'key_use_agent', + Help: 'When set forces the usage of the ssh-agent.\n\nWhen key-file is also set, the ".pub" file of the specified key-file is read and only the associated key is\nrequested from the ssh-agent. This allows to avoid `Too many authentication failures for *username*` errors\nwhen the ssh-agent contains many keys.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'use_insecure_cipher', + Help: 'Enable the use of the aes128-cbc cipher. This cipher is insecure and may allow plaintext data to be recovered by an attacker.', + Provider: '', + Default: false, + Value: null, + Examples: [ + { + Value: 'false', + Help: 'Use default Cipher list.', + Provider: '', + }, + { + Value: 'true', + Help: 'Enables the use of the aes128-cbc cipher.', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'disable_hashcheck', + Help: 'Disable the execution of SSH commands to determine if remote file hashing is available.\nLeave blank or set to false to enable hashing (recommended), set to true to disable hashing.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'ask_password', + Help: 'Allow asking for SFTP password when needed.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'path_override', + Help: 'Override path used by SSH connection.\n\nThis allows checksum calculation when SFTP and SSH paths are\ndifferent. This issue affects among others Synology NAS boxes.\n\nShared folders can be found in directories representing volumes\n\n rclone sync /home/local/directory remote:/directory --ssh-path-override /volume2/directory\n\nHome directory can be found in a shared folder called "home"\n\n rclone sync /home/local/directory remote:/home/directory --ssh-path-override /volume1/homes/USER/directory', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + { + Name: 'set_modtime', + Help: 'Set the modified time on the remote if set.', + Provider: '', + Default: true, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, + { + Name: 'union', + Description: + 'A stackable unification remote, which can appear to merge the contents of several remotes', + Prefix: 'union', + Options: [ + { + Name: 'remotes', + Help: "List of space separated remotes.\nCan be 'remotea:test/dir remoteb:', '\"remotea:test/space dir\" remoteb:', etc.\nThe last remote is used to write to.", + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'webdav', + Description: 'Webdav', + Prefix: 'webdav', + Options: [ + { + Name: 'url', + Help: 'URL of http host to connect to', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'https://example.com', + Help: 'Connect to example.com', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: true, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'vendor', + Help: 'Name of the Webdav site/service/software you are using', + Provider: '', + Default: '', + Value: null, + Examples: [ + { + Value: 'nextcloud', + Help: 'Nextcloud', + Provider: '', + }, + { + Value: 'owncloud', + Help: 'Owncloud', + Provider: '', + }, + { + Value: 'sharepoint', + Help: 'Sharepoint', + Provider: '', + }, + { + Value: 'other', + Help: 'Other site/service or software', + Provider: '', + }, + ], + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'user', + Help: 'User name', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'pass', + Help: 'Password.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: true, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'bearer_token', + Help: 'Bearer token instead of user/pass (eg a Macaroon)', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + ], + }, + { + Name: 'yandex', + Description: 'Yandex Disk', + Prefix: 'yandex', + Options: [ + { + Name: 'client_id', + Help: 'Yandex Client Id\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'client_secret', + Help: 'Yandex Client Secret\nLeave blank normally.', + Provider: '', + Default: '', + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: false, + }, + { + Name: 'unlink', + Help: 'Remove existing public link to file/folder with link command rather than creating.\nDefault is false, meaning link command will create or retrieve public link.', + Provider: '', + Default: false, + Value: null, + ShortOpt: '', + Hide: 0, + Required: false, + IsPassword: false, + NoPrefix: false, + Advanced: true, + }, + ], + }, +]; diff --git a/api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts b/api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts new file mode 100644 index 0000000000..7a6c4b0c6c --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts @@ -0,0 +1,427 @@ +import { RuleEffect, type SchemaBasedCondition, type JsonSchema, JsonSchema7 } from '@jsonforms/core'; +import type { DataSlice, SettingSlice, UIElement } from '@app/unraid-api/types/json-forms.js'; +import { mergeSettingSlices } from '@app/unraid-api/types/json-forms.js'; +import { config as rcloneConfig } from './config.js'; + +/** + * Type definitions for RClone config options + */ +export interface RCloneOptionDef { + Name: string; + Help: string; + Provider: string; + Default: any; + Value: any | null; + ShortOpt: string; + Hide: number; + Required: boolean; + IsPassword: boolean; + NoPrefix: boolean; + Advanced: boolean; + Type?: string; + Options?: string[]; +} + +export interface RCloneProviderDef { + Name: string; + Description: string; + Prefix: string; + Options: RCloneOptionDef[]; +} + +/** + * Translates RClone config option to JsonSchema properties + */ +function translateRCloneOptionToJsonSchema(option: RCloneOptionDef): JsonSchema7 { + const schema: JsonSchema7 = { + type: getJsonSchemaType(option.Type || 'string'), + title: option.Name, + description: option.Help || '', + }; + + // Add default value if available + if (option.Default !== undefined && option.Default !== '') { + schema.default = option.Default; + } + + // Add enum values if available + if (option.Options && option.Options.length > 0) { + schema.enum = option.Options; + } + + // Add validation constraints + if (option.Required) { + if (schema.type === 'string') { + schema.minLength = 1; + } else if (schema.type === 'number') { + schema.minimum = 0; + } + } + + return schema; +} + +/** + * Get available provider types from RClone config + */ +export function getAvailableProviderTypes(): string[] { + return rcloneConfig.map(provider => provider.Name); +} + +/** + * Get provider options for a specific provider + */ +export function getProviderOptions(providerName: string): Record { + const provider = rcloneConfig.find(p => p.Name === providerName); + if (!provider) return {}; + + return provider.Options.reduce((acc, option) => { + acc[option.Name] = option; + return acc; + }, {} as Record); +} + +/** + * Generates the UI schema for RClone remote configuration + */ +export function getRcloneConfigFormSchema( + providerTypes: string[] = [], + selectedProvider: string = '', + providerOptions: Record = {} +): SettingSlice { + // If provider types not provided, get from config + if (providerTypes.length === 0) { + providerTypes = getAvailableProviderTypes(); + } + + // Combine all form slices for the complete schema + const slices = [ + getBasicConfigSlice(providerTypes), + getProviderConfigSlice(selectedProvider, providerOptions), + getAdvancedConfigSlice(selectedProvider, providerOptions), + ]; + + return mergeSettingSlices(slices); +} + +/** + * Step 1: Basic configuration - name and type selection + */ +function getBasicConfigSlice(providerTypes: string[]): SettingSlice { + // Create UI elements for basic configuration (Step 1) + const basicConfigElements: UIElement[] = [ + { + type: 'Control', + scope: '#/properties/name', + label: 'Name of this remote (For your reference)', + options: { + placeholder: 'Enter a name', + }, + }, + { + type: 'Control', + scope: '#/properties/type', + label: 'Storage Provider Type', + options: { + format: 'dropdown', + description: 'Select the cloud storage provider to use for this remote.', + }, + }, + { + type: 'Label', + text: 'Documentation', + options: { + format: 'documentation', + description: 'For more information, refer to the RClone Config Documentation.', + }, + }, + ]; + + // Define the data schema for basic configuration + const basicConfigProperties: Record = { + name: { + type: 'string', + title: 'Remote Name', + description: 'Name to identify this remote configuration', + pattern: '^[a-zA-Z0-9_-]+$', + minLength: 1, + maxLength: 50, + }, + type: { + type: 'string', + title: 'Provider Type', + default: providerTypes.length > 0 ? providerTypes[0] : '', + oneOf: providerTypes.map(type => ({ const: type, title: type })) + }, + }; + + return { + properties: basicConfigProperties as unknown as DataSlice, + elements: basicConfigElements, + }; +} + +/** + * Step 2: Provider-specific configuration based on the selected provider + */ +function getProviderConfigSlice( + selectedProvider: string, + providerOptions: Record +): SettingSlice { + // Default elements for when a provider isn't selected or options aren't loaded + let providerConfigElements: UIElement[] = [ + { + type: 'Label', + text: 'Provider Configuration', + options: { + format: 'loading', + description: 'Select a provider type first to see provider-specific options.', + }, + }, + ]; + + // Default properties when no provider is selected + let providerConfigProperties: Record = {}; + + // If we have a selected provider and options for it + if (selectedProvider && Object.keys(providerOptions).length > 0) { + // Create dynamic UI elements based on provider options + providerConfigElements = Object.entries(providerOptions).map(([key, option]) => { + if (option.Advanced === true) { + return null; // Skip advanced options for this step + } + + return { + type: 'Control', + scope: `#/properties/parameters/properties/${key}`, + label: option.Help || key, + options: { + placeholder: option.Default?.toString() || '', + description: option.Help || '', + required: option.Required || false, + format: getFormatForType(option.Type, option.Options), + hide: option.Hide === 1, + }, + }; + }).filter(Boolean) as UIElement[]; + + // No options available case + if (providerConfigElements.length === 0) { + providerConfigElements = [ + { + type: 'Label', + text: 'No Configuration Required', + options: { + description: 'This provider does not require additional configuration, or all options are advanced.', + }, + }, + ]; + } + + // Create dynamic properties schema based on provider options + const paramProperties: Record = {}; + + Object.entries(providerOptions).forEach(([key, option]) => { + if (option.Advanced === true) { + return; // Skip advanced options for this step + } + + paramProperties[key] = translateRCloneOptionToJsonSchema(option); + }); + + providerConfigProperties = { + parameters: { + type: 'object', + properties: paramProperties, + }, + }; + } + + return { + properties: providerConfigProperties as unknown as DataSlice, + elements: providerConfigElements, + }; +} + +/** + * Step 3: Advanced configuration options for the selected provider + */ +function getAdvancedConfigSlice( + selectedProvider: string, + providerOptions: Record +): SettingSlice { + // Default elements when no advanced options are available + let advancedConfigElements: UIElement[] = [ + { + type: 'Label', + text: 'Advanced Configuration', + options: { + format: 'note', + description: 'No advanced options available for this provider.', + }, + }, + ]; + + // Default properties + let advancedConfigProperties: Record = {}; + + // If we have a selected provider and options + if (selectedProvider && Object.keys(providerOptions).length > 0) { + // Create dynamic UI elements for advanced options + const advancedElements = Object.entries(providerOptions).map(([key, option]) => { + if (!option.Advanced) { + return null; // Skip non-advanced options + } + + return { + type: 'Control', + scope: `#/properties/parameters/properties/${key}`, + label: option.Help || key, + options: { + placeholder: option.Default?.toString() || '', + description: option.Help || '', + required: option.Required || false, + format: getFormatForType(option.Type, option.Options), + hide: option.Hide === 1, + }, + }; + }).filter(Boolean) as UIElement[]; + + // Use default message if no advanced options + if (advancedElements.length > 0) { + advancedConfigElements = advancedElements; + } + + // Create dynamic properties schema for advanced options + const advancedProperties: Record = {}; + + Object.entries(providerOptions).forEach(([key, option]) => { + if (!option.Advanced) { + return; // Skip non-advanced options + } + + advancedProperties[key] = translateRCloneOptionToJsonSchema(option); + }); + + // Only add if we have advanced options + if (Object.keys(advancedProperties).length > 0) { + advancedConfigProperties = { + parameters: { + type: 'object', + properties: advancedProperties, + }, + }; + } + } + + return { + properties: advancedConfigProperties as unknown as DataSlice, + elements: advancedConfigElements, + }; +} + +/** + * Helper function to convert RClone option types to JSON Schema types + */ +function getJsonSchemaType(rcloneType: string): string { + switch (rcloneType?.toLowerCase()) { + case 'int': + case 'size': + case 'duration': + return 'number'; + case 'bool': + return 'boolean'; + case 'string': + case 'text': + default: + return 'string'; + } +} + +/** + * Helper function to get the appropriate UI format based on RClone option type + */ +function getFormatForType(rcloneType: string = '', options: string[] | null = null): string { + if (options && options.length > 0) { + return 'dropdown'; + } + + switch (rcloneType?.toLowerCase()) { + case 'int': + case 'size': + return 'number'; + case 'duration': + return 'duration'; + case 'bool': + return 'checkbox'; + case 'password': + return 'password'; + case 'text': + return 'textarea'; + default: + return 'text'; + } +} + +/** + * Returns a combined form schema for the rclone backup configuration UI + */ +export function getRcloneConfigSlice(): SettingSlice { + const elements: UIElement[] = [ + { + type: 'Label', + text: 'Configure RClone Backup', + options: { + format: 'title', + description: 'This 3-step process will guide you through setting up your RClone backup configuration.', + }, + }, + { + type: 'Control', + scope: '#/properties/configStep', + label: 'Configuration Step', + options: { + format: 'stepper', + steps: [ + { label: 'Set up Remote Config', description: 'Name and provider selection' }, + { label: 'Set up Drive', description: 'Provider-specific configuration' }, + { label: 'Advanced Config', description: 'Optional advanced settings' }, + ], + }, + }, + { + type: 'Control', + scope: '#/properties/showAdvanced', + label: 'Edit Advanced Options', + options: { + format: 'checkbox', + }, + rule: { + effect: RuleEffect.SHOW, + condition: { + scope: '#/properties/configStep', + schema: { enum: [1] } // Only show on step 2 + } as SchemaBasedCondition, + }, + }, + ]; + + // Basic properties for the rclone backup configuration + const properties: Record = { + configStep: { + type: 'number', + minimum: 0, + maximum: 2, + default: 0, + }, + showAdvanced: { + type: 'boolean', + default: false, + }, + }; + + return { + properties: properties as unknown as DataSlice, + elements, + }; +} diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts new file mode 100644 index 0000000000..30aa83baf6 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts @@ -0,0 +1,272 @@ +import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import crypto from 'crypto'; +import { existsSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; + +import { execa } from 'execa'; +import got from 'got'; +import { RCloneProvider, RCloneProviderOption, RCloneProviderOptionExample, RCloneProviderResponse, RCloneProviderOptionResponse, RCloneProviderTypes } from '@app/unraid-api/graph/resolvers/rclone/rclone.model.js'; + +@Injectable() +export class RCloneApiService implements OnModuleInit, OnModuleDestroy { + private isInitialized: boolean = false; + private readonly logger = new Logger(RCloneApiService.name); + private rcloneSocketPath: string = ''; + private rcloneBaseUrl: string = ''; + private rcloneUsername: string = 'unraid-rclone'; + private rclonePassword: string = crypto.randomBytes(32).toString('hex'); + constructor() {} + + async onModuleInit(): Promise { + try { + const { getters } = await import('@app/store/index.js'); + // Check if Rclone Socket is running, if not, start it. + this.rcloneSocketPath = getters.paths()['rclone-socket']; + const logFilePath = join(getters.paths()['log-base'], 'rclone-unraid-api.log'); + this.logger.log(`RClone socket path: ${this.rcloneSocketPath}`); + this.logger.log(`RClone log file path: ${logFilePath}`); + + // Format the base URL for Unix socket + this.rcloneBaseUrl = `http://unix:${this.rcloneSocketPath}:/`; + + const socketRunning = await this.checkRcloneSocket(this.rcloneSocketPath); + if (!socketRunning) { + this.startRcloneSocket(this.rcloneSocketPath, logFilePath); + } + this.isInitialized = true; + } catch (error: unknown) { + this.logger.error(`Error initializing FlashBackupService: ${error}`); + this.isInitialized = false; + } + } + + async onModuleDestroy(): Promise { + if (this.isInitialized) { + await this.stopRcloneSocket(this.rcloneSocketPath); + } + this.logger.log('FlashBackupService module destroyed'); + } + + /** + * Starts the RClone RC daemon on the specified socket path + */ + private async startRcloneSocket(socketPath: string, logFilePath: string): Promise { + // Make log file exists + if (!existsSync(logFilePath)) { + this.logger.debug(`Creating log file: ${logFilePath}`); + await mkdir(dirname(logFilePath), { recursive: true }); + await writeFile(logFilePath, '', 'utf-8'); + } + this.logger.log(`Starting RClone RC daemon on socket: ${socketPath}`); + await execa('rclone', [ + 'rcd', + '--rc-addr', + socketPath, + '--log-level', + 'INFO', + '--rc-user', + this.rcloneUsername, + '--rc-pass', + this.rclonePassword, + '--log-file', + logFilePath, + '--rc-web-gui', + '--rc-web-gui-no-open-browser', + ]); + } + + private async stopRcloneSocket(socketPath: string): Promise { + this.logger.log(`Stopping RClone RC daemon on socket: ${socketPath}`); + execa('rclone', ['rcd', '--rc-addr', socketPath, '--stop']); + } + + /** + * Checks if the RClone socket exists and is running + */ + private async checkRcloneSocket(socketPath: string): Promise { + const socketExists = existsSync(socketPath); + if (!socketExists) { + this.logger.warn(`RClone socket does not exist at: ${socketPath}`); + return false; + } + try { + const socketIsRunning = await execa('rclone', ['about']); + return socketIsRunning.exitCode === 0; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.logger.error(`Error checking RClone socket: ${errorMessage}`); + return false; + } + } + + /** + * Get providers supported by RClone + */ + async getProviders(): Promise { + const response = await this.callRcloneApi('config/providers') as { providers: RCloneProviderResponse[] }; + return response?.providers?.map(provider => ({ + name: provider.Name, + description: provider.Description, + prefix: provider.Prefix, + options: this.mapProviderOptions(provider.Options), + })) || []; + } + + /** + * Get provider types as a simple array + */ + async getProviderTypes(): Promise { + const providers = await this.getProviders(); + const types: string[] = providers.map(provider => provider.prefix); + return { types }; + } + + /** + * Maps RClone provider options from API format to our model format + */ + private mapProviderOptions(options: RCloneProviderOptionResponse[]): RCloneProviderOption[] { + return options.map(option => ({ + name: option.Name, + help: option.Help, + provider: option.Provider, + default: option.Default, + value: option.Value, + shortOpt: option.ShortOpt, + hide: option.Hide, + required: option.Required, + isPassword: option.IsPassword, + noPrefix: option.NoPrefix, + advanced: option.Advanced, + defaultStr: option.DefaultStr, + valueStr: option.ValueStr, + type: option.Type, + examples: option.Examples?.map(example => ({ + value: example.Value, + help: example.Help, + provider: example.Provider, + })), + })); + } + + /** + * List all configured remotes + */ + async listRemotes(): Promise { + const response = await this.callRcloneApi('config/listremotes'); + return response.remotes || []; + } + + /** + * Get detailed config for a specific remote + */ + async getRemoteConfig(name: string): Promise { + return this.callRcloneApi('config/get', { name }); + } + + /** + * Create a new remote configuration + */ + async createRemote(name: string, type: string, parameters: Record = {}): Promise { + // Combine the required parameters for the create request + const params = { + name, + type, + ...parameters, + }; + + this.logger.log(`Creating new remote: ${name} of type: ${type}`); + return this.callRcloneApi('config/create', params); + } + + /** + * Update an existing remote configuration + */ + async updateRemote(name: string, parameters: Record = {}): Promise { + const params = { + name, + ...parameters, + }; + + this.logger.log(`Updating remote: ${name}`); + return this.callRcloneApi('config/update', params); + } + + /** + * Delete a remote configuration + */ + async deleteRemote(name: string): Promise { + this.logger.log(`Deleting remote: ${name}`); + return this.callRcloneApi('config/delete', { name }); + } + + /** + * Start a backup operation using sync/copy + * This copies a directory from source to destination + */ + async startBackup( + srcPath: string, + dstPath: string, + options: Record = {} + ): Promise { + this.logger.log(`Starting backup from ${srcPath} to ${dstPath}`); + const params = { + srcFs: srcPath, + dstFs: dstPath, + ...options, + }; + + return this.callRcloneApi('sync/copy', params); + } + + /** + * Get the status of a running job + */ + async getJobStatus(jobId: string): Promise { + return this.callRcloneApi('job/status', { jobid: jobId }); + } + + /** + * List all running jobs + */ + async listRunningJobs(): Promise { + return this.callRcloneApi('job/list'); + } + + /** + * Generic method to call the RClone RC API + */ + private async callRcloneApi(endpoint: string, params: Record = {}): Promise { + try { + const url = `${this.rcloneBaseUrl}/${endpoint}`; + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + + const response = await got.post(url, { + json: params, + responseType: 'json', + enableUnixSockets: true, + headers: { + Authorization: `Basic ${Buffer.from(`${this.rcloneUsername}:${this.rclonePassword}`).toString('base64')}`, + }, + }); + + return response.body; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.logger.error(`Error calling RClone API (${endpoint}): ${errorMessage} ${error}`); + throw error; + } + } + + async serveWebGui(): Promise<{ url: string; username: string; password: string }> { + if (!this.isInitialized) { + throw new Error('RClone service is not initialized'); + } + + return { + url: this.rcloneBaseUrl, + username: this.rcloneUsername, + password: this.rclonePassword, + }; + } +} diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts new file mode 100644 index 0000000000..8e93308a19 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts @@ -0,0 +1,64 @@ +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; +import { Logger } from '@nestjs/common'; + +import { Layout } from '@jsonforms/core'; +import { GraphQLJSON } from 'graphql-scalars'; + +import { + AuthActionVerb, + AuthPossession, + UsePermissions, +} from '@app/unraid-api/graph/directives/use-permissions.directive.js'; +import { Resource } from '@app/unraid-api/graph/resolvers/base.model.js'; +import { RCloneBackupConfigForm, RCloneRemote, CreateRCloneRemoteInput } from '@app/unraid-api/graph/resolvers/rclone/rclone.model.js'; +import { RCloneService } from '@app/unraid-api/graph/resolvers/rclone/rclone.service.js'; +import { RCloneApiService } from '@app/unraid-api/graph/resolvers/rclone/rclone-api.service.js'; +import { DataSlice } from '@app/unraid-api/types/json-forms.js'; +import { RCloneFormService } from '@app/unraid-api/graph/resolvers/rclone/rclone-form.service.js'; + +@Resolver(() => RCloneBackupConfigForm) +export class RCloneConfigResolver { + private readonly logger = new Logger(RCloneConfigResolver.name); + + constructor( + private readonly rcloneService: RCloneService, + private readonly rcloneApiService: RCloneApiService, + private readonly rcloneFormService: RCloneFormService + ) {} + + @ResolveField(() => GraphQLJSON) + async dataSchema( + @Parent() _parent: RCloneBackupConfigForm + ): Promise<{ properties: DataSlice; type: 'object' }> { + const schema = await this.rcloneFormService.dataSchema(); + return { + properties: schema.properties as DataSlice, + type: 'object', + }; + } + + @ResolveField(() => GraphQLJSON) + async uiSchema(@Parent() _parent: RCloneBackupConfigForm): Promise { + return this.rcloneFormService.uiSchema(); + } + + @Mutation(() => RCloneRemote) + @UsePermissions({ + action: AuthActionVerb.CREATE, + resource: Resource.FLASH, + possession: AuthPossession.ANY, + }) + async createRCloneRemote(@Args('input') input: CreateRCloneRemoteInput): Promise { + try { + await this.rcloneApiService.createRemote(input.name, input.type, input.config); + return { + name: input.name, + type: input.type, + config: input.config + }; + } catch (error) { + this.logger.error(`Error creating remote: ${error}`); + throw new Error(`Failed to create remote: ${error}`); + } + } +} diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts new file mode 100644 index 0000000000..15815c3b8d --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts @@ -0,0 +1,118 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { type Layout } from '@jsonforms/core'; + +import type { SettingSlice } from '@app/unraid-api/types/json-forms.js'; +import { mergeSettingSlices } from '@app/unraid-api/types/json-forms.js'; + +import { + getRcloneConfigFormSchema, + getRcloneConfigSlice, + getAvailableProviderTypes, +} from './jsonforms/rclone-jsonforms-config.js'; +import { RCloneApiService } from './rclone-api.service.js'; + +/** + * Service responsible for generating form UI schemas and form logic + */ +@Injectable() +export class RCloneFormService { + private readonly logger = new Logger(RCloneFormService.name); + private _providerTypes: string[] = []; + private _providerOptions: Record = {}; + + constructor( + private readonly rcloneApiService: RCloneApiService + ) {} + + /** + * Loads RClone provider types and options + */ + private async loadProviderInfo(): Promise { + try { + const providersResponse = await this.rcloneApiService.getProviders(); + if (providersResponse) { + // Extract provider types + this._providerTypes = providersResponse.map(provider => provider.name); + this._providerOptions = providersResponse.reduce((acc, provider) => { + acc[provider.name] = provider.options; + return acc; + }, {}); + this.logger.debug(`Loaded ${this._providerTypes.length} provider types`); + } + } catch (error) { + this.logger.error(`Error loading provider information: ${error}`); + throw error; + } + } + + /** + * Builds the complete settings schema + */ + async buildSettingsSchema( + providerTypes: string[] = [], + selectedProvider: string = '', + providerOptions: Record = {} + ): Promise { + // Get the stepper UI and the form based on the selected provider + const baseSlice = getRcloneConfigSlice(); + const formSlice = getRcloneConfigFormSchema( + providerTypes, + selectedProvider, + providerOptions + ); + + return mergeSettingSlices([baseSlice, formSlice]); + } + + /** + * Returns the data schema for the form + */ + async dataSchema( + providerTypes: string[] = [], + selectedProvider: string = '', + providerOptions: Record = {} + ): Promise> { + // If provider types weren't provided and we haven't loaded them yet, try to load them + if (providerTypes.length === 0 && this._providerTypes.length === 0) { + await this.loadProviderInfo(); + providerTypes = this._providerTypes; + providerOptions = this._providerOptions; + } + + const { properties } = await this.buildSettingsSchema( + providerTypes, + selectedProvider, + providerOptions + ); + return { + type: 'object', + properties, + }; + } + + /** + * Returns the UI schema for the form + */ + async uiSchema( + providerTypes: string[] = [], + selectedProvider: string = '', + providerOptions: Record = {} + ): Promise { + // If provider types weren't provided and we haven't loaded them yet, try to load them + if (providerTypes.length === 0 && this._providerTypes.length === 0) { + await this.loadProviderInfo(); + providerTypes = this._providerTypes; + providerOptions = this._providerOptions; + } + + const { elements } = await this.buildSettingsSchema( + providerTypes, + selectedProvider, + providerOptions + ); + return { + type: 'VerticalLayout', + elements, + }; + } +} \ No newline at end of file diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts new file mode 100644 index 0000000000..3b265ace61 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts @@ -0,0 +1,199 @@ +import { Field, ID, InputType, ObjectType } from '@nestjs/graphql'; + + + +import { type Layout } from '@jsonforms/core'; +import { GraphQLJSON } from 'graphql-scalars'; + + + +import { DataSlice } from '@app/unraid-api/types/json-forms.js'; + + + + + +@ObjectType() +export class RCloneDrive { + @Field(() => String, { description: 'Provider name' }) + name!: string; + + @Field(() => GraphQLJSON, { description: 'Provider options and configuration schema' }) + options!: Record; +} + +/** + * Raw response format from rclone API + */ +export interface RCloneProviderResponse { + Name: string; + Description: string; + Prefix: string; + Options: RCloneProviderOptionResponse[]; + CommandHelp?: string | null; + Aliases?: string[] | null; + Hide?: boolean; + MetadataInfo?: Record; +} + +/** + * Raw option format from rclone API + */ +export interface RCloneProviderOptionResponse { + Name: string; + Help: string; + Provider: string; + Default?: unknown; + Value?: unknown; + ShortOpt?: string; + Hide?: boolean; + Required?: boolean; + IsPassword?: boolean; + NoPrefix?: boolean; + Advanced?: boolean; + DefaultStr?: string; + ValueStr?: string; + Type?: string; + Examples?: Array<{ Value: string; Help: string; Provider: string }>; +} + +@ObjectType() +export class RCloneProviderOption { + @Field(() => String) + name!: string; + + @Field(() => String) + help!: string; + + @Field(() => String) + provider!: string; + + @Field(() => GraphQLJSON, { nullable: true }) + default?: unknown; + + @Field(() => GraphQLJSON, { nullable: true }) + value?: unknown; + + @Field(() => String, { nullable: true }) + shortOpt?: string; + + @Field(() => Boolean, { nullable: true }) + hide?: boolean; + + @Field(() => Boolean, { nullable: true }) + required?: boolean; + + @Field(() => Boolean, { nullable: true }) + isPassword?: boolean; + + @Field(() => Boolean, { nullable: true }) + noPrefix?: boolean; + + @Field(() => Boolean, { nullable: true }) + advanced?: boolean; + + @Field(() => String, { nullable: true }) + defaultStr?: string; + + @Field(() => String, { nullable: true }) + valueStr?: string; + + @Field(() => String, { nullable: true }) + type?: string; + + @Field(() => [RCloneProviderOptionExample], { nullable: true }) + examples?: RCloneProviderOptionExample[]; +} + +@ObjectType() +export class RCloneProviderOptionExample { + @Field(() => String) + value!: string; + + @Field(() => String) + help!: string; + + @Field(() => String) + provider!: string; +} + +@ObjectType() +export class RCloneProviderTypes { + @Field(() => [String], { description: 'List of all provider types' }) + types!: string[]; +} + +/** + * { + Name: 'jottacloud', + Description: 'Jottacloud', + Prefix: 'jottacloud', + Options: [Array], + CommandHelp: null, + Aliases: null, + Hide: false, + MetadataInfo: [Object] + }, + */ +@ObjectType() +export class RCloneProvider { + @Field(() => String) + name!: string; + + @Field(() => String) + description!: string; + + @Field(() => String) + prefix!: string; + + @Field(() => [RCloneProviderOption]) + options!: RCloneProviderOption[]; +} + +@ObjectType() +export class RCloneBackupConfigForm { + @Field(() => ID) + id!: string; + + @Field(() => GraphQLJSON) + dataSchema!: { properties: DataSlice; type: 'object' }; + + @Field(() => GraphQLJSON) + uiSchema!: Layout; +} + +@ObjectType() +export class RCloneBackupSettings { + @Field(() => RCloneBackupConfigForm) + configForm!: RCloneBackupConfigForm; + + @Field(() => [RCloneDrive]) + drives!: RCloneDrive[]; + + @Field(() => [String]) + remotes!: string[]; +} + +@ObjectType() +export class RCloneRemote { + @Field(() => String) + name!: string; + + @Field(() => String) + type!: string; + + @Field(() => GraphQLJSON) + config!: Record; +} + +@InputType() +export class CreateRCloneRemoteInput { + @Field(() => String) + name!: string; + + @Field(() => String) + type!: string; + + @Field(() => GraphQLJSON) + config!: Record; +} \ No newline at end of file diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts new file mode 100644 index 0000000000..601fe7b820 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; + +import { RCloneService } from '@app/unraid-api/graph/resolvers/rclone/rclone.service.js'; +import { RCloneApiService } from '@app/unraid-api/graph/resolvers/rclone/rclone-api.service.js'; +import { RCloneFormService } from '@app/unraid-api/graph/resolvers/rclone/rclone-form.service.js'; +import { RCloneBackupSettingsResolver } from '@app/unraid-api/graph/resolvers/rclone/rclone.resolver.js'; +import { RCloneConfigResolver } from '@app/unraid-api/graph/resolvers/rclone/rclone-config.resolver.js'; + +@Module({ + imports: [], + providers: [ + RCloneService, + RCloneApiService, + RCloneFormService, + RCloneBackupSettingsResolver, + RCloneConfigResolver + ], + exports: [RCloneService, RCloneApiService] +}) +export class RCloneModule {} diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts new file mode 100644 index 0000000000..ec51cfa889 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts @@ -0,0 +1,69 @@ +import { Logger } from '@nestjs/common'; +import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; + +import { + AuthActionVerb, + AuthPossession, + UsePermissions, +} from '@app/unraid-api/graph/directives/use-permissions.directive.js'; +import { Resource } from '@app/unraid-api/graph/resolvers/base.model.js'; +import { RCloneApiService } from '@app/unraid-api/graph/resolvers/rclone/rclone-api.service.js'; +import { + RCloneBackupSettings, + RCloneBackupConfigForm, + RCloneDrive, +} from '@app/unraid-api/graph/resolvers/rclone/rclone.model.js'; + +import { RCloneService } from './rclone.service.js'; + +@Resolver(() => RCloneBackupSettings) +export class RCloneBackupSettingsResolver { + private readonly logger = new Logger(RCloneBackupSettingsResolver.name); + + constructor( + private readonly rcloneService: RCloneService, + private readonly rcloneApiService: RCloneApiService + ) {} + + @Query(() => RCloneBackupSettings) + @UsePermissions({ + action: AuthActionVerb.READ, + resource: Resource.FLASH, + possession: AuthPossession.ANY, + }) + async rcloneBackup(): Promise { + return {} as RCloneBackupSettings; + } + + @ResolveField(() => RCloneBackupConfigForm) + async configForm(@Parent() _parent: RCloneBackupSettings): Promise { + return { + id: 'rcloneBackupConfigForm', + } as RCloneBackupConfigForm; + } + + @ResolveField(() => [RCloneDrive]) + async drives(@Parent() _parent: RCloneBackupSettings): Promise { + try { + const providers = await this.rcloneApiService.getProviders(); + + return Object.entries(providers).map(([name, options]) => ({ + name, + options: options as Record, + })); + } catch (error) { + this.logger.error(`Error getting providers: ${error}`); + return []; + } + } + + @ResolveField(() => [String]) + async remotes(@Parent() _parent: RCloneBackupSettings): Promise { + try { + return await this.rcloneApiService.listRemotes(); + } catch (error) { + this.logger.error(`Error listing remotes: ${error}`); + return []; + } + } +} diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts new file mode 100644 index 0000000000..c8ce4cec5f --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts @@ -0,0 +1,89 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { type Layout } from '@jsonforms/core'; + +import type { SettingSlice } from '@app/unraid-api/types/json-forms.js'; +import { RCloneApiService } from '@app/unraid-api/graph/resolvers/rclone/rclone-api.service.js'; +import { RCloneFormService } from '@app/unraid-api/graph/resolvers/rclone/rclone-form.service.js'; + +/** + * Types for rclone backup configuration UI + */ +export interface RcloneBackupConfigValues { + configStep: number; + showAdvanced: boolean; + name?: string; + type?: string; + parameters?: Record; +} + +@Injectable() +export class RCloneService { + private readonly logger = new Logger(RCloneService.name); + private _providerTypes: string[] = []; + private _providerOptions: Record = {}; + + constructor( + private readonly rcloneApiService: RCloneApiService, + private readonly rcloneFormService: RCloneFormService + ) {} + + /** + * Get provider types + */ + get providerTypes(): string[] { + return this._providerTypes; + } + + /** + * Get provider options + */ + get providerOptions(): Record { + return this._providerOptions; + } + + /** + * Initializes the service by loading provider information + */ + async onModuleInit(): Promise { + try { + await this.loadProviderInfo(); + } catch (error) { + this.logger.error(`Failed to initialize RcloneBackupSettingsService: ${error}`); + } + } + + /** + * Loads RClone provider types and options + */ + private async loadProviderInfo(): Promise { + try { + const providersResponse = await this.rcloneApiService.getProviders(); + if (providersResponse) { + // Extract provider types + this._providerTypes = Object.keys(providersResponse); + this._providerOptions = providersResponse; + this.logger.debug(`Loaded ${this._providerTypes.length} provider types`); + } + } catch (error) { + this.logger.error(`Error loading provider information: ${error}`); + throw error; + } + } + + /** + * Gets current configuration values + */ + async getCurrentSettings(): Promise { + return { + configStep: 0, + showAdvanced: false, + }; + } + + /** + * Gets a list of configured remotes + */ + async getConfiguredRemotes(): Promise { + return this.rcloneApiService.listRemotes(); + } +} diff --git a/api/src/unraid-api/graph/resolvers/resolvers.module.ts b/api/src/unraid-api/graph/resolvers/resolvers.module.ts index 464fb7eee2..04bd2dc60f 100644 --- a/api/src/unraid-api/graph/resolvers/resolvers.module.ts +++ b/api/src/unraid-api/graph/resolvers/resolvers.module.ts @@ -4,9 +4,6 @@ import { AuthModule } from '@app/unraid-api/auth/auth.module.js'; import { ApiKeyModule } from '@app/unraid-api/graph/resolvers/api-key/api-key.module.js'; import { ApiKeyResolver } from '@app/unraid-api/graph/resolvers/api-key/api-key.resolver.js'; import { ArrayModule } from '@app/unraid-api/graph/resolvers/array/array.module.js'; -import { ArrayMutationsResolver } from '@app/unraid-api/graph/resolvers/array/array.mutations.resolver.js'; -import { ArrayResolver } from '@app/unraid-api/graph/resolvers/array/array.resolver.js'; -import { ArrayService } from '@app/unraid-api/graph/resolvers/array/array.service.js'; import { CloudResolver } from '@app/unraid-api/graph/resolvers/cloud/cloud.resolver.js'; import { ConfigResolver } from '@app/unraid-api/graph/resolvers/config/config.resolver.js'; import { ConnectModule } from '@app/unraid-api/graph/resolvers/connect/connect.module.js'; @@ -14,6 +11,7 @@ import { CustomizationModule } from '@app/unraid-api/graph/resolvers/customizati import { DisksModule } from '@app/unraid-api/graph/resolvers/disks/disks.module.js'; import { DisplayResolver } from '@app/unraid-api/graph/resolvers/display/display.resolver.js'; import { DockerModule } from '@app/unraid-api/graph/resolvers/docker/docker.module.js'; +import { FlashBackupModule } from '@app/unraid-api/graph/resolvers/flash-backup/flash-backup.module.js'; import { FlashResolver } from '@app/unraid-api/graph/resolvers/flash/flash.resolver.js'; import { InfoResolver } from '@app/unraid-api/graph/resolvers/info/info.resolver.js'; import { LogsResolver } from '@app/unraid-api/graph/resolvers/logs/logs.resolver.js'; @@ -24,6 +22,7 @@ import { NotificationsResolver } from '@app/unraid-api/graph/resolvers/notificat import { NotificationsService } from '@app/unraid-api/graph/resolvers/notifications/notifications.service.js'; import { OnlineResolver } from '@app/unraid-api/graph/resolvers/online/online.resolver.js'; import { OwnerResolver } from '@app/unraid-api/graph/resolvers/owner/owner.resolver.js'; +import { RCloneModule } from '@app/unraid-api/graph/resolvers/rclone/rclone.module.js'; import { RegistrationResolver } from '@app/unraid-api/graph/resolvers/registration/registration.resolver.js'; import { ServerResolver } from '@app/unraid-api/graph/resolvers/servers/server.resolver.js'; import { VarsResolver } from '@app/unraid-api/graph/resolvers/vars/vars.resolver.js'; @@ -35,7 +34,17 @@ import { SharesResolver } from '@app/unraid-api/graph/shares/shares.resolver.js' import { MeResolver } from '@app/unraid-api/graph/user/user.resolver.js'; @Module({ - imports: [ArrayModule, ApiKeyModule, ConnectModule, CustomizationModule, DockerModule, DisksModule], + imports: [ + ArrayModule, + ApiKeyModule, + AuthModule, + ConnectModule, + CustomizationModule, + DockerModule, + DisksModule, + FlashBackupModule, + RCloneModule, + ], providers: [ CloudResolver, ConfigResolver, diff --git a/api/src/unraid-api/rest/rest.controller.ts b/api/src/unraid-api/rest/rest.controller.ts index c2d87b1fc5..15f681514e 100644 --- a/api/src/unraid-api/rest/rest.controller.ts +++ b/api/src/unraid-api/rest/rest.controller.ts @@ -1,16 +1,19 @@ -import { Controller, Get, Logger, Param, Res } from '@nestjs/common'; +import { Controller, Get, Logger, Param, Res, Req, All } from '@nestjs/common'; import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz'; -import type { FastifyReply } from '@app/unraid-api/types/fastify.js'; +import type { FastifyReply, FastifyRequest } from '@app/unraid-api/types/fastify.js'; import { Public } from '@app/unraid-api/auth/public.decorator.js'; import { Resource } from '@app/unraid-api/graph/resolvers/base.model.js'; import { RestService } from '@app/unraid-api/rest/rest.service.js'; +import got from 'got'; @Controller() export class RestController { protected logger = new Logger(RestController.name); - constructor(private readonly restService: RestService) {} + constructor( + private readonly restService: RestService, + ) {} @Get('/') @Public() @@ -53,4 +56,45 @@ export class RestController { return res.status(500).send(`Error: Failed to get customizations`); } } +/* + @All('/graphql/api/rclone-webgui/*') + @UsePermissions({ + action: AuthActionVerb.READ, + resource: Resource.FLASH, + possession: AuthPossession.ANY, + }) + async proxyRcloneWebGui(@Req() req: FastifyRequest, @Res() res: FastifyReply) { + try { + const rcloneDetails = await this.flashBackupService.serveWebGui(); + const path = req.url.replace('/graphql/api/rclone-webgui/', ''); + const targetUrl = `${rcloneDetails.url}${path}`; + + this.logger.debug(`Proxying request to: ${targetUrl}`); + + // Forward the request to the RClone service + const method = req.method.toLowerCase(); + const options = { + headers: { + ...req.headers, + Authorization: `Basic ${Buffer.from(`${rcloneDetails.username}:${rcloneDetails.password}`).toString('base64')}`, + }, + body: req.body, + responseType: 'buffer', + enableUnixSockets: true, + }; + + const response = await got[method](targetUrl, options); + + // Forward the response back to the client + return res + .status(response.statusCode) + .headers(response.headers) + .send(response.body); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + this.logger.error(`Error proxying to RClone WebGUI: ${errorMessage}`); + return res.status(500).send(`Error: Failed to proxy to RClone WebGUI`); + } + } + */ } diff --git a/api/src/unraid-api/rest/rest.module.ts b/api/src/unraid-api/rest/rest.module.ts index 8f3b3292e5..b6065369c4 100644 --- a/api/src/unraid-api/rest/rest.module.ts +++ b/api/src/unraid-api/rest/rest.module.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { RestController } from '@app/unraid-api/rest/rest.controller.js'; import { RestService } from '@app/unraid-api/rest/rest.service.js'; - +import { RCloneModule } from '@app/unraid-api/graph/resolvers/rclone/rclone.module.js'; @Module({ - imports: [], + imports: [RCloneModule], controllers: [RestController], providers: [RestService], }) diff --git a/web/components/RClone/RCloneConfig.vue b/web/components/RClone/RCloneConfig.vue new file mode 100644 index 0000000000..6dacce7f60 --- /dev/null +++ b/web/components/RClone/RCloneConfig.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/web/components/RClone/graphql/settings.query.ts b/web/components/RClone/graphql/settings.query.ts new file mode 100644 index 0000000000..cd88e2a10b --- /dev/null +++ b/web/components/RClone/graphql/settings.query.ts @@ -0,0 +1,35 @@ +import { graphql } from "~/composables/gql/gql"; + + +export const GET_RCLONE_CONFIG_FORM = graphql(/* GraphQL */ ` + query GetRCloneConfigForm { + rcloneBackup { + configForm { + dataSchema + uiSchema + } + drives { + name + options + } + } + } +`); + +export const LIST_REMOTES = graphql(/* GraphQL */ ` + query ListRCloneRemotes { + rcloneBackup { + remotes + } + } +`); + +export const CREATE_REMOTE = graphql(/* GraphQL */ ` + mutation CreateRCloneRemote($input: CreateRCloneRemoteInput!) { + createRCloneRemote(input: $input) { + name + type + config + } + } +`); \ No newline at end of file diff --git a/web/composables/gql/gql.ts b/web/composables/gql/gql.ts index f9818e13f2..96885e9243 100644 --- a/web/composables/gql/gql.ts +++ b/web/composables/gql/gql.ts @@ -36,6 +36,9 @@ type Documents = { "\n mutation RecomputeOverview {\n recalculateOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": typeof types.RecomputeOverviewDocument, "\n subscription NotificationAddedSub {\n notificationAdded {\n ...NotificationFragment\n }\n }\n": typeof types.NotificationAddedSubDocument, "\n subscription NotificationOverviewSub {\n notificationsOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": typeof types.NotificationOverviewSubDocument, + "\n query GetRCloneConfigForm {\n rcloneBackup {\n configForm { \n dataSchema\n uiSchema\n }\n drives {\n name\n options\n }\n }\n }\n": typeof types.GetRCloneConfigFormDocument, + "\n query ListRCloneRemotes {\n rcloneBackup {\n remotes\n }\n }\n": typeof types.ListRCloneRemotesDocument, + "\n mutation CreateRCloneRemote($input: CreateRCloneRemoteInput!) {\n createRCloneRemote(input: $input) {\n name\n type\n config\n }\n }\n": typeof types.CreateRCloneRemoteDocument, "\n mutation ConnectSignIn($input: ConnectSignInInput!) {\n connectSignIn(input: $input)\n }\n": typeof types.ConnectSignInDocument, "\n mutation SignOut {\n connectSignOut\n }\n": typeof types.SignOutDocument, "\n fragment PartialCloud on Cloud {\n error\n apiKey {\n valid\n error\n }\n cloud {\n status\n error\n }\n minigraphql {\n status\n error\n }\n relay {\n status\n error\n }\n }\n": typeof types.PartialCloudFragmentDoc, @@ -69,6 +72,9 @@ const documents: Documents = { "\n mutation RecomputeOverview {\n recalculateOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": types.RecomputeOverviewDocument, "\n subscription NotificationAddedSub {\n notificationAdded {\n ...NotificationFragment\n }\n }\n": types.NotificationAddedSubDocument, "\n subscription NotificationOverviewSub {\n notificationsOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": types.NotificationOverviewSubDocument, + "\n query GetRCloneConfigForm {\n rcloneBackup {\n configForm { \n dataSchema\n uiSchema\n }\n drives {\n name\n options\n }\n }\n }\n": types.GetRCloneConfigFormDocument, + "\n query ListRCloneRemotes {\n rcloneBackup {\n remotes\n }\n }\n": types.ListRCloneRemotesDocument, + "\n mutation CreateRCloneRemote($input: CreateRCloneRemoteInput!) {\n createRCloneRemote(input: $input) {\n name\n type\n config\n }\n }\n": types.CreateRCloneRemoteDocument, "\n mutation ConnectSignIn($input: ConnectSignInInput!) {\n connectSignIn(input: $input)\n }\n": types.ConnectSignInDocument, "\n mutation SignOut {\n connectSignOut\n }\n": types.SignOutDocument, "\n fragment PartialCloud on Cloud {\n error\n apiKey {\n valid\n error\n }\n cloud {\n status\n error\n }\n minigraphql {\n status\n error\n }\n relay {\n status\n error\n }\n }\n": types.PartialCloudFragmentDoc, @@ -182,6 +188,18 @@ export function graphql(source: "\n subscription NotificationAddedSub {\n no * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n subscription NotificationOverviewSub {\n notificationsOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n"): (typeof documents)["\n subscription NotificationOverviewSub {\n notificationsOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query GetRCloneConfigForm {\n rcloneBackup {\n configForm { \n dataSchema\n uiSchema\n }\n drives {\n name\n options\n }\n }\n }\n"): (typeof documents)["\n query GetRCloneConfigForm {\n rcloneBackup {\n configForm { \n dataSchema\n uiSchema\n }\n drives {\n name\n options\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query ListRCloneRemotes {\n rcloneBackup {\n remotes\n }\n }\n"): (typeof documents)["\n query ListRCloneRemotes {\n rcloneBackup {\n remotes\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n mutation CreateRCloneRemote($input: CreateRCloneRemoteInput!) {\n createRCloneRemote(input: $input) {\n name\n type\n config\n }\n }\n"): (typeof documents)["\n mutation CreateRCloneRemote($input: CreateRCloneRemoteInput!) {\n createRCloneRemote(input: $input) {\n name\n type\n config\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/web/composables/gql/graphql.ts b/web/composables/gql/graphql.ts index 0d38408a07..b0be839776 100644 --- a/web/composables/gql/graphql.ts +++ b/web/composables/gql/graphql.ts @@ -20,8 +20,6 @@ export type Scalars = { DateTime: { input: string; output: string; } /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ JSON: { input: any; output: any; } - /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ - JSONObject: { input: any; output: any; } /** A field whose value is a valid TCP port within the range of 0 to 65535: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_ports */ Port: { input: number; output: number; } /** @@ -527,6 +525,12 @@ export type CreateApiKeyInput = { roles?: InputMaybe>; }; +export type CreateRCloneRemoteInput = { + config: Scalars['JSON']['input']; + name: Scalars['String']['input']; + type: Scalars['String']['input']; +}; + export type Customization = { __typename?: 'Customization'; activationCode?: Maybe; @@ -673,10 +677,10 @@ export type DockerContainer = Node & { id: Scalars['PrefixedID']['output']; image: Scalars['String']['output']; imageId: Scalars['String']['output']; - labels?: Maybe; - mounts?: Maybe>; + labels?: Maybe; + mounts?: Maybe>; names: Array; - networkSettings?: Maybe; + networkSettings?: Maybe; ports: Array; /** Total size of all the files in the container */ sizeRootFs?: Maybe; @@ -705,19 +709,19 @@ export type DockerMutationsStopArgs = { export type DockerNetwork = Node & { __typename?: 'DockerNetwork'; attachable: Scalars['Boolean']['output']; - configFrom: Scalars['JSONObject']['output']; + configFrom: Scalars['JSON']['output']; configOnly: Scalars['Boolean']['output']; - containers: Scalars['JSONObject']['output']; + containers: Scalars['JSON']['output']; created: Scalars['String']['output']; driver: Scalars['String']['output']; enableIPv6: Scalars['Boolean']['output']; id: Scalars['PrefixedID']['output']; ingress: Scalars['Boolean']['output']; internal: Scalars['Boolean']['output']; - ipam: Scalars['JSONObject']['output']; - labels: Scalars['JSONObject']['output']; + ipam: Scalars['JSON']['output']; + labels: Scalars['JSON']['output']; name: Scalars['String']['output']; - options: Scalars['JSONObject']['output']; + options: Scalars['JSON']['output']; scope: Scalars['String']['output']; }; @@ -752,6 +756,14 @@ export type Flash = Node & { vendor: Scalars['String']['output']; }; +export type FlashBackupStatus = { + __typename?: 'FlashBackupStatus'; + /** Job ID if available, can be used to check job status. */ + jobId?: Maybe; + /** Status message indicating the outcome of the backup initiation. */ + status: Scalars['String']['output']; +}; + export type Gpu = Node & { __typename?: 'Gpu'; blacklisted: Scalars['Boolean']['output']; @@ -828,6 +840,17 @@ export type InfoMemory = Node & { used: Scalars['BigInt']['output']; }; +export type InitiateFlashBackupInput = { + /** Destination path on the remote. */ + destinationPath: Scalars['String']['input']; + /** Additional options for the backup operation, such as --dry-run or --transfers. */ + options?: InputMaybe; + /** The name of the remote configuration to use for the backup. */ + remoteName: Scalars['String']['input']; + /** Source path to backup (typically the flash drive). */ + sourcePath: Scalars['String']['input']; +}; + export type KeyFile = { __typename?: 'KeyFile'; contents?: Maybe; @@ -901,11 +924,14 @@ export type Mutation = { connectSignOut: Scalars['Boolean']['output']; /** Creates a new notification record */ createNotification: Notification; + createRCloneRemote: RCloneRemote; /** Deletes all archived notifications on server. */ deleteArchivedNotifications: NotificationOverview; deleteNotification: NotificationOverview; docker: DockerMutations; enableDynamicRemoteAccess: Scalars['Boolean']['output']; + /** Initiates a flash drive backup using a configured remote. */ + initiateFlashBackup: FlashBackupStatus; parityCheck: ParityCheckMutations; /** Reads each notification to recompute & update the overview. */ recalculateOverview: NotificationOverview; @@ -946,6 +972,11 @@ export type MutationCreateNotificationArgs = { }; +export type MutationCreateRCloneRemoteArgs = { + input: CreateRCloneRemoteInput; +}; + + export type MutationDeleteNotificationArgs = { id: Scalars['PrefixedID']['input']; type: NotificationType; @@ -957,6 +988,16 @@ export type MutationEnableDynamicRemoteAccessArgs = { }; +export type MutationInitiateFlashBackupArgs = { + input: InitiateFlashBackupInput; +}; + + +export type MutationRemoveRoleFromApiKeyArgs = { + input: RemoveRoleFromApiKeyInput; +}; + + export type MutationSetAdditionalAllowedOriginsArgs = { input: AllowedOriginInput; }; @@ -1199,6 +1240,7 @@ export type Query = { parityHistory: Array; publicPartnerInfo?: Maybe; publicTheme: Theme; + rcloneBackup: RCloneBackupSettings; registration?: Maybe; remoteAccess: RemoteAccess; server?: Maybe; @@ -1227,6 +1269,61 @@ export type QueryLogFileArgs = { startLine?: InputMaybe; }; +export type RCloneBackupConfigForm = { + __typename?: 'RCloneBackupConfigForm'; + dataSchema: Scalars['JSON']['output']; + id: Scalars['ID']['output']; + uiSchema: Scalars['JSON']['output']; +}; + +export type RCloneBackupSettings = { + __typename?: 'RCloneBackupSettings'; + configForm: RCloneBackupConfigForm; + drives: Array; + remotes: Array; +}; + +export type RCloneDrive = { + __typename?: 'RCloneDrive'; + /** Provider name */ + name: Scalars['String']['output']; + /** Provider options and configuration schema */ + options: Scalars['JSON']['output']; +}; + +export type RCloneProviderOption = { + __typename?: 'RCloneProviderOption'; + advanced?: Maybe; + default?: Maybe; + defaultStr?: Maybe; + examples?: Maybe>; + help: Scalars['String']['output']; + hide?: Maybe; + isPassword?: Maybe; + name: Scalars['String']['output']; + noPrefix?: Maybe; + provider: Scalars['String']['output']; + required?: Maybe; + shortOpt?: Maybe; + type?: Maybe; + value?: Maybe; + valueStr?: Maybe; +}; + +export type RCloneProviderOptionExample = { + __typename?: 'RCloneProviderOptionExample'; + help: Scalars['String']['output']; + provider: Scalars['String']['output']; + value: Scalars['String']['output']; +}; + +export type RCloneRemote = { + __typename?: 'RCloneRemote'; + config: Scalars['JSON']['output']; + name: Scalars['String']['output']; + type: Scalars['String']['output']; +}; + export type Registration = Node & { __typename?: 'Registration'; expiration?: Maybe; @@ -1961,6 +2058,23 @@ export type NotificationOverviewSubSubscription = { __typename?: 'Subscription', & { ' $fragmentRefs'?: { 'NotificationCountFragmentFragment': NotificationCountFragmentFragment } } ) } }; +export type GetRCloneConfigFormQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetRCloneConfigFormQuery = { __typename?: 'Query', rcloneBackup: { __typename?: 'RCloneBackupSettings', configForm: { __typename?: 'RCloneBackupConfigForm', dataSchema: any, uiSchema: any }, drives: Array<{ __typename?: 'RCloneDrive', name: string, options: any }> } }; + +export type ListRCloneRemotesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type ListRCloneRemotesQuery = { __typename?: 'Query', rcloneBackup: { __typename?: 'RCloneBackupSettings', remotes: Array } }; + +export type CreateRCloneRemoteMutationVariables = Exact<{ + input: CreateRCloneRemoteInput; +}>; + + +export type CreateRCloneRemoteMutation = { __typename?: 'Mutation', createRCloneRemote: { __typename?: 'RCloneRemote', name: string, type: string, config: any } }; + export type ConnectSignInMutationVariables = Exact<{ input: ConnectSignInInput; }>; @@ -2035,6 +2149,9 @@ export const OverviewDocument = {"kind":"Document","definitions":[{"kind":"Opera export const RecomputeOverviewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RecomputeOverview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"recalculateOverview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationCountFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationCountFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationCountFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationCounts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"alert"}}]}}]} as unknown as DocumentNode; export const NotificationAddedSubDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"NotificationAddedSub"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notificationAdded"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Notification"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"formattedTimestamp"}}]}}]} as unknown as DocumentNode; export const NotificationOverviewSubDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"NotificationOverviewSub"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notificationsOverview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationCountFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationCountFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationCountFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationCounts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"alert"}}]}}]} as unknown as DocumentNode; +export const GetRCloneConfigFormDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRCloneConfigForm"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"rcloneBackup"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configForm"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dataSchema"}},{"kind":"Field","name":{"kind":"Name","value":"uiSchema"}}]}},{"kind":"Field","name":{"kind":"Name","value":"drives"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"options"}}]}}]}}]}}]} as unknown as DocumentNode; +export const ListRCloneRemotesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ListRCloneRemotes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"rcloneBackup"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"remotes"}}]}}]}}]} as unknown as DocumentNode; +export const CreateRCloneRemoteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateRCloneRemote"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateRCloneRemoteInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createRCloneRemote"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"config"}}]}}]}}]} as unknown as DocumentNode; export const ConnectSignInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ConnectSignIn"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ConnectSignInInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignIn"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const SignOutDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SignOut"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignOut"}}]}}]} as unknown as DocumentNode; export const ServerStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"serverState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PartialCloud"}}]}},{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"os"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hostname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updateExpiration"}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialCloud"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"minigraphql"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode; diff --git a/web/helpers/create-apollo-client.ts b/web/helpers/create-apollo-client.ts index aa9a138f3c..4fd16ec3c0 100644 --- a/web/helpers/create-apollo-client.ts +++ b/web/helpers/create-apollo-client.ts @@ -74,6 +74,7 @@ const splitLinks = split( wsLink, httpLink ); + /** * @todo as we add retries, determine which we'll need * https://www.apollographql.com/docs/react/api/link/introduction/#additive-composition diff --git a/web/pages/flashbackup.vue b/web/pages/flashbackup.vue new file mode 100644 index 0000000000..925fb1f7bd --- /dev/null +++ b/web/pages/flashbackup.vue @@ -0,0 +1,24 @@ + + + From f93c850b95d64cb4a839c5e3f2c7e90ddd391820 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 15 Apr 2025 15:17:52 -0400 Subject: [PATCH 03/55] chore: rclone config almost complete --- api/generated-schema.graphql | 8 +- api/package.json | 2 - .../rclone/rclone-config.resolver.ts | 94 +++++++++++++++++-- .../graph/resolvers/rclone/rclone.model.ts | 22 ++--- .../graph/resolvers/rclone/rclone.resolver.ts | 20 ++-- pnpm-lock.yaml | 30 +----- web/components/RClone/RCloneConfig.vue | 69 +++++++++++--- .../RClone/graphql/settings.query.ts | 4 +- web/composables/gql/gql.ts | 6 +- web/composables/gql/graphql.ts | 27 +++++- 10 files changed, 197 insertions(+), 85 deletions(-) diff --git a/api/generated-schema.graphql b/api/generated-schema.graphql index 53ae1e3b8c..a9f1e58410 100644 --- a/api/generated-schema.graphql +++ b/api/generated-schema.graphql @@ -1379,12 +1379,12 @@ type RCloneProviderOptionExample { type RCloneBackupConfigForm { id: ID! - dataSchema: JSON! - uiSchema: JSON! + dataSchema(providerType: String, parameters: JSON): JSON! + uiSchema(providerType: String, parameters: JSON): JSON! } type RCloneBackupSettings { - configForm: RCloneBackupConfigForm! + configForm(providerType: String, parameters: JSON): RCloneBackupConfigForm! drives: [RCloneDrive!]! remotes: [String!]! } @@ -1751,7 +1751,7 @@ input InitiateFlashBackupInput { input CreateRCloneRemoteInput { name: String! type: String! - config: JSON! + parameters: JSON! } type Subscription { diff --git a/api/package.json b/api/package.json index 04e108585c..9f832285ce 100644 --- a/api/package.json +++ b/api/package.json @@ -109,8 +109,6 @@ "graphql-scalars": "^1.23.0", "graphql-subscriptions": "^3.0.0", "graphql-tag": "^2.12.6", - "graphql-type-json": "^0.3.2", - "graphql-type-uuid": "^0.2.0", "graphql-ws": "^6.0.0", "ini": "^5.0.0", "ip": "^2.0.1", diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts index 8e93308a19..140de170f8 100644 --- a/api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts @@ -1,4 +1,4 @@ -import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; +import { Args, Context, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { Logger } from '@nestjs/common'; import { Layout } from '@jsonforms/core'; @@ -28,18 +28,94 @@ export class RCloneConfigResolver { @ResolveField(() => GraphQLJSON) async dataSchema( - @Parent() _parent: RCloneBackupConfigForm + @Parent() _parent: RCloneBackupConfigForm, + @Args('providerType', { nullable: true }) argProviderType?: string, + @Args('parameters', { type: () => GraphQLJSON, nullable: true }) argParameters?: Record, + @Context() context?: any ): Promise<{ properties: DataSlice; type: 'object' }> { - const schema = await this.rcloneFormService.dataSchema(); - return { - properties: schema.properties as DataSlice, - type: 'object', - }; + try { + // Get providerType and parameters from parent query if not provided directly + let providerType = argProviderType || ''; + let parameters = argParameters || {}; + + // Check for these values in the parent query context + if (context?.variableValues) { + providerType = providerType || context.variableValues.providerType || ''; + parameters = parameters || context.variableValues.parameters || {}; + } + + this.logger.debug(`dataSchema using providerType: ${providerType}, parameters: ${JSON.stringify(parameters)}`); + + // Get provided types + let providerTypes: string[] = []; + try { + const providers = await this.rcloneApiService.getProviderTypes(); + providerTypes = providers.types; + } catch (error) { + this.logger.warn(`Could not get provider types for dataSchema: ${error}`); + } + + // Get the schema with provider information + const schema = await this.rcloneFormService.dataSchema( + providerTypes, + providerType, + parameters + ); + + return { + properties: schema.properties as DataSlice, + type: 'object', + }; + } catch (error) { + this.logger.error(`Error generating dataSchema: ${error}`); + return { + properties: {} as DataSlice, + type: 'object', + }; + } } @ResolveField(() => GraphQLJSON) - async uiSchema(@Parent() _parent: RCloneBackupConfigForm): Promise { - return this.rcloneFormService.uiSchema(); + async uiSchema( + @Parent() _parent: RCloneBackupConfigForm, + @Args('providerType', { nullable: true }) argProviderType?: string, + @Args('parameters', { type: () => GraphQLJSON, nullable: true }) argParameters?: Record, + @Context() context?: any + ): Promise { + try { + // Get providerType and parameters from parent query if not provided directly + let providerType = argProviderType || ''; + let parameters = argParameters || {}; + + // Check for these values in the parent query context + if (context?.variableValues) { + providerType = providerType || context.variableValues.providerType || ''; + parameters = parameters || context.variableValues.parameters || {}; + } + + this.logger.debug(`uiSchema using providerType: ${providerType}, parameters: ${JSON.stringify(parameters)}`); + + // Get provided types + let providerTypes: string[] = []; + try { + const providers = await this.rcloneApiService.getProviderTypes(); + providerTypes = providers.types; + } catch (error) { + this.logger.warn(`Could not get provider types for uiSchema: ${error}`); + } + + return this.rcloneFormService.uiSchema( + providerTypes, + providerType, + parameters + ); + } catch (error) { + this.logger.error(`Error generating uiSchema: ${error}`); + return { + type: 'VerticalLayout', + elements: [], + }; + } } @Mutation(() => RCloneRemote) diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts index 3b265ace61..58115f69d3 100644 --- a/api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts @@ -1,18 +1,10 @@ import { Field, ID, InputType, ObjectType } from '@nestjs/graphql'; - - import { type Layout } from '@jsonforms/core'; import { GraphQLJSON } from 'graphql-scalars'; - - import { DataSlice } from '@app/unraid-api/types/json-forms.js'; - - - - @ObjectType() export class RCloneDrive { @Field(() => String, { description: 'Provider name' }) @@ -169,7 +161,7 @@ export class RCloneBackupSettings { @Field(() => [RCloneDrive]) drives!: RCloneDrive[]; - + @Field(() => [String]) remotes!: string[]; } @@ -178,10 +170,10 @@ export class RCloneBackupSettings { export class RCloneRemote { @Field(() => String) name!: string; - + @Field(() => String) type!: string; - + @Field(() => GraphQLJSON) config!: Record; } @@ -190,10 +182,10 @@ export class RCloneRemote { export class CreateRCloneRemoteInput { @Field(() => String) name!: string; - + @Field(() => String) type!: string; - + @Field(() => GraphQLJSON) - config!: Record; -} \ No newline at end of file + parameters!: Record; +} diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts index ec51cfa889..a1a819bb1d 100644 --- a/api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts @@ -1,5 +1,6 @@ import { Logger } from '@nestjs/common'; -import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Args, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { GraphQLJSON } from 'graphql-scalars'; import { AuthActionVerb, @@ -15,6 +16,7 @@ import { } from '@app/unraid-api/graph/resolvers/rclone/rclone.model.js'; import { RCloneService } from './rclone.service.js'; +import { RCloneFormService } from './rclone-form.service.js'; @Resolver(() => RCloneBackupSettings) export class RCloneBackupSettingsResolver { @@ -22,7 +24,8 @@ export class RCloneBackupSettingsResolver { constructor( private readonly rcloneService: RCloneService, - private readonly rcloneApiService: RCloneApiService + private readonly rcloneApiService: RCloneApiService, + private readonly rcloneFormService: RCloneFormService ) {} @Query(() => RCloneBackupSettings) @@ -36,7 +39,12 @@ export class RCloneBackupSettingsResolver { } @ResolveField(() => RCloneBackupConfigForm) - async configForm(@Parent() _parent: RCloneBackupSettings): Promise { + async configForm( + @Parent() _parent: RCloneBackupSettings, + @Args('providerType', { nullable: true }) providerType?: string, + @Args('parameters', { type: () => GraphQLJSON, nullable: true }) parameters?: Record + ): Promise { + // Return basic form info without generating schema data - this will be handled by RCloneConfigResolver return { id: 'rcloneBackupConfigForm', } as RCloneBackupConfigForm; @@ -47,9 +55,9 @@ export class RCloneBackupSettingsResolver { try { const providers = await this.rcloneApiService.getProviders(); - return Object.entries(providers).map(([name, options]) => ({ - name, - options: options as Record, + return providers.map(provider => ({ + name: provider.name, + options: provider.options as unknown as Record, })); } catch (error) { this.logger.error(`Error getting providers: ${error}`); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae157a0aea..0e652be69a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,12 +204,6 @@ importers: graphql-tag: specifier: ^2.12.6 version: 2.12.6(graphql@16.10.0) - graphql-type-json: - specifier: ^0.3.2 - version: 0.3.2(graphql@16.10.0) - graphql-type-uuid: - specifier: ^0.2.0 - version: 0.2.0(graphql@16.10.0) graphql-ws: specifier: ^6.0.0 version: 6.0.4(graphql@16.10.0)(ws@8.18.1) @@ -4240,7 +4234,6 @@ packages: '@unraid/libvirt@2.1.0': resolution: {integrity: sha512-yF/sAzYukM+VpDdAebf0z0O2mE5mGOEAW5lIafFkYoMiu60zNkNmr5IoA9hWCMmQkBOUCam8RZGs9YNwRjMtMQ==} engines: {node: '>=14'} - cpu: [x64, arm64] os: [linux, darwin] '@unraid/shared-callbacks@1.1.1': @@ -7476,16 +7469,6 @@ packages: peerDependencies: graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - graphql-type-json@0.3.2: - resolution: {integrity: sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==} - peerDependencies: - graphql: '>=0.8.0' - - graphql-type-uuid@0.2.0: - resolution: {integrity: sha512-AHTJn95nj5y/M4d2nqWYzWJNd/C/9XA7aYIdVxnySeDFXv/lq5ZlL9RtH/iNvD1pbEZIkZmnPHqJKPYQDelEUg==} - peerDependencies: - graphql: '>=0.8.0' - graphql-ws@6.0.4: resolution: {integrity: sha512-8b4OZtNOvv8+NZva8HXamrc0y1jluYC0+13gdh7198FKjVzXyTvVc95DCwGzaKEfn3YuWZxUqjJlHe3qKM/F2g==} engines: {node: '>=20'} @@ -10082,7 +10065,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.13.0: @@ -15662,7 +15644,7 @@ snapshots: '@stylistic/eslint-plugin@4.2.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.23.0(jiti@2.4.2) eslint-visitor-keys: 4.2.0 espree: 10.3.0 @@ -18982,7 +18964,7 @@ snapshots: eslint-plugin-import-x@4.8.1(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3): dependencies: '@types/doctrine': 0.0.9 - '@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.0(supports-color@9.4.0) doctrine: 3.0.0 eslint: 9.23.0(jiti@2.4.2) @@ -20026,14 +20008,6 @@ snapshots: graphql: 16.10.0 tslib: 2.8.1 - graphql-type-json@0.3.2(graphql@16.10.0): - dependencies: - graphql: 16.10.0 - - graphql-type-uuid@0.2.0(graphql@16.10.0): - dependencies: - graphql: 16.10.0 - graphql-ws@6.0.4(graphql@16.10.0)(ws@8.18.1): dependencies: graphql: 16.10.0 diff --git a/web/components/RClone/RCloneConfig.vue b/web/components/RClone/RCloneConfig.vue index 6dacce7f60..9d86214781 100644 --- a/web/components/RClone/RCloneConfig.vue +++ b/web/components/RClone/RCloneConfig.vue @@ -1,18 +1,20 @@