// src/schemas/registry.ts // Backend Schema Registry — single source of truth for all rclone backend field definitions. // IMPORTANT: FieldDef.key values MUST match rclone config key names exactly. // These keys are used by Phase 2 generators to build rclone.conf INI content. // Verify against https://rclone.org/azureblob/ and https://rclone.org/s3/ before Phase 2. // // BackendType is DERIVED from keyof typeof BACKEND_REGISTRY — do NOT add a manual union. // BackendCategory is the grouping used for UI categorization and search. export type BackendCategory = 'cloud-object-storage' | 'cloud-drives' | 'protocol-based'; export interface FieldDef { key: string; // MUST match rclone config key exactly (snake_case) label: string; inputType: 'text' | 'password' | 'select' | 'toggle'; required: boolean; placeholder?: string; helpText?: string; options?: { value: string; label: string }[]; // for inputType: 'select' validate?: { regex: RegExp; message: string }; tooltipText?: string; } export interface BackendMeta { displayName: string; description: string; category: BackendCategory; fields: FieldDef[]; } export const BACKEND_REGISTRY = { azureblob: { displayName: 'Azure Blob Storage', description: 'Microsoft Azure cloud storage', category: 'cloud-object-storage' as const, fields: [ { key: 'account', label: 'Storage Account Name', inputType: 'text' as const, required: true, placeholder: 'mystorageaccount', helpText: 'The storage account name (not the full URL)', validate: { regex: /^[a-z0-9]{3,24}$/, message: 'Must be 3–24 lowercase alphanumeric characters (no hyphens or uppercase)', }, }, { key: 'key', label: 'Access Key', inputType: 'password' as const, required: false, helpText: 'Base64-encoded storage account key. Provide either this or a SAS URL, not both.', tooltipText: 'The full storage account key grants unrestricted read/write access to all containers in the account. Keep this secret. If you only need limited access, use a SAS URL instead.', }, { key: 'sas_url', label: 'SAS URL', inputType: 'password' as const, required: false, placeholder: 'https://mystorageaccount.blob.core.windows.net/?sv=...', helpText: 'Full SAS URL including account and container. Provide either this or an access key, not both.', tooltipText: 'A SAS URL (Shared Access Signature) bundles the storage endpoint with a time-limited, scope-limited token. It grants access only to containers you specify and expires automatically. Use this if you want limited-access credentials. If you have the full account key, switch to Access Key.', }, ], }, s3: { displayName: 'Amazon S3', description: 'AWS Simple Storage Service', category: 'cloud-object-storage' as const, fields: [ { key: 'provider', label: 'Provider', inputType: 'select' as const, required: true, options: [{ value: 'AWS', label: 'Amazon S3' }], }, { key: 'access_key_id', label: 'Access Key ID', inputType: 'text' as const, required: true, placeholder: 'AKIAIOSFODNN7EXAMPLE', helpText: 'The access key ID from your IAM credentials (starts with AKIA for long-term keys).', tooltipText: 'Find this in the AWS Console under IAM > Users > select user > Security credentials > Access keys. If you only have the secret key, you cannot recover the Key ID — create a new access key pair instead.', }, { key: 'secret_access_key', label: 'Secret Access Key', inputType: 'password' as const, required: true, helpText: 'The secret access key paired with your Access Key ID. Only shown once at creation time.', tooltipText: 'This value is shown only once when you create an access key in IAM. If you did not save it, delete the existing key and create a new one. Never share or commit this value — treat it as a password.', }, { key: 'region', label: 'Region', inputType: 'text' as const, required: true, placeholder: 'us-east-1', tooltipText: 'The AWS region where your S3 bucket is located. Find this in the S3 console next to your bucket name (e.g. us-east-1, eu-west-2, ap-southeast-1).', validate: { regex: /^[a-z][a-z0-9-]+[a-z0-9]$/, message: 'Must be a valid AWS region format (e.g. us-east-1)', }, }, ], }, 's3-compatible': { displayName: 'S3-Compatible', description: 'Wasabi, MinIO, Cloudflare R2, and others', category: 'cloud-object-storage' as const, fields: [ { key: 'provider', label: 'Provider', inputType: 'select' as const, required: true, options: [{ value: 'Other', label: 'S3-Compatible' }], helpText: 'Covers Wasabi, MinIO, Cloudflare R2, and any S3-compatible storage', }, { key: 'access_key_id', label: 'Access Key ID', inputType: 'text' as const, required: true, helpText: 'The access key ID from your storage provider\'s dashboard.', tooltipText: 'Where to find this varies by provider: Wasabi — Account > Access Keys; Cloudflare R2 — R2 > Manage R2 API Tokens; MinIO — admin console > Identity > Users or Service Accounts.', }, { key: 'secret_access_key', label: 'Secret Access Key', inputType: 'password' as const, required: true, helpText: 'The secret key paired with your Access Key ID.', tooltipText: 'This value is typically shown only once when you create the key. Store it securely — if lost, you will need to generate a new key pair.', }, { key: 'endpoint', label: 'Endpoint URL', inputType: 'text' as const, required: true, placeholder: 'https://s3.wasabisys.com', helpText: 'The S3-compatible endpoint URL for your storage provider', tooltipText: 'Common endpoints: Wasabi — https://s3.wasabisys.com (or region-specific, e.g. https://s3.us-west-1.wasabisys.com); Cloudflare R2 — https://{account_id}.r2.cloudflarestorage.com; MinIO — your server URL with port (e.g. https://minio.example.com:9000).', }, { key: 'region', label: 'Region', inputType: 'text' as const, required: false, placeholder: 'us-east-1', helpText: 'Optional for most S3-compatible providers', }, ], }, gcs: { displayName: 'Google Cloud Storage', description: 'Google Cloud Storage', category: 'cloud-object-storage' as const, fields: [ { key: 'project_number', label: 'Project Number', inputType: 'text' as const, required: true, placeholder: '123456789', helpText: 'Your GCP project number (not project ID)', tooltipText: 'Found in Google Cloud Console > select your project > Dashboard > Project info card. This is the numeric ID (e.g. 123456789012) — not the text-based project ID like "my-project-name".', validate: { regex: /^\d+$/, message: 'Must contain digits only', }, }, { key: 'service_account_credentials', label: 'Service Account JSON', inputType: 'password' as const, required: true, placeholder: '{"type":"service_account","project_id":"..."}', helpText: 'Paste the full content of your service account JSON key file', tooltipText: 'Create a service account key at Google Cloud Console > IAM & Admin > Service Accounts > select or create an account > Keys > Add Key > Create new key > JSON. Paste the entire contents of the downloaded JSON file here. See https://cloud.google.com/iam/docs/keys-create-delete for detailed steps.', }, ], }, b2: { displayName: 'Backblaze B2', description: 'Backblaze B2 Cloud Storage', category: 'cloud-object-storage' as const, fields: [ { key: 'account', label: 'Application Key ID', inputType: 'text' as const, required: true, placeholder: 'your-application-key-id', helpText: 'The applicationKeyId — not the master account ID', tooltipText: 'Found in Backblaze B2 > App Keys. The applicationKeyId is the shorter alphanumeric ID listed next to your key — not the master account ID shown in your account settings.', }, { key: 'key', label: 'Application Key', inputType: 'password' as const, required: true, helpText: 'The application key (secret value from the B2 dashboard)', tooltipText: 'The application key value is shown only once when you create a new app key in B2 > App Keys. Copy it immediately — it cannot be retrieved later. If lost, delete the key and create a new one.', }, ], }, 'azure-files': { displayName: 'Azure Files', description: 'Microsoft Azure Files (SMB/REST cloud file shares)', category: 'cloud-object-storage' as const, fields: [ { key: 'account', label: 'Storage Account Name', inputType: 'text' as const, required: true, placeholder: 'mystorageaccount', helpText: 'The Azure storage account name', validate: { regex: /^[a-z0-9]{3,24}$/, message: 'Must be 3–24 lowercase alphanumeric characters', }, }, { key: 'key', label: 'Storage Account Key', inputType: 'password' as const, required: true, helpText: 'The base64-encoded storage account key from the Azure portal', tooltipText: 'Found in Azure Portal > your storage account > Security + Networking > Access keys. Use key1 or key2 — either works.', }, ], }, swift: { displayName: 'OpenStack Swift', description: 'OpenStack Swift object storage', category: 'cloud-object-storage' as const, fields: [ { key: 'user', label: 'Username', inputType: 'text' as const, required: true, helpText: 'The OpenStack username (or tenant:user for v2 auth)', }, { key: 'key', label: 'API Key / Password', inputType: 'password' as const, required: true, helpText: 'The OpenStack API key or password for this user', }, { key: 'auth', label: 'Auth URL', inputType: 'text' as const, required: true, placeholder: 'https://auth.example.com/v3', helpText: 'The OpenStack identity (Keystone) auth endpoint URL', tooltipText: 'This is the Keystone endpoint for your OpenStack provider (v2 or v3). Check your provider\'s dashboard or ask your administrator.', }, { key: 'tenant', label: 'Tenant / Project Name', inputType: 'text' as const, required: true, helpText: 'The OpenStack tenant (v2) or project name (v3)', }, { key: 'region', label: 'Region', inputType: 'text' as const, required: false, helpText: 'The OpenStack region (optional — leave blank if your provider has a single region)', }, ], }, onedrive: { displayName: 'OneDrive', description: 'Microsoft OneDrive (paste pre-obtained rclone token)', category: 'cloud-drives' as const, fields: [ { key: 'token', label: 'OAuth Token (JSON)', inputType: 'password' as const, required: true, placeholder: '{"access_token":"...","token_type":"Bearer","refresh_token":"...","expiry":"..."}', helpText: 'Paste the JSON token from: rclone authorize "onedrive"', tooltipText: 'This is the JSON token obtained by running `rclone authorize "onedrive"` on a machine with a browser. The command opens a browser window, you authenticate, and rclone prints a JSON token — paste that entire JSON blob here.', }, { key: 'drive_id', label: 'Drive ID', inputType: 'text' as const, required: true, placeholder: 'b!...', helpText: 'The drive ID from your OneDrive. Found in the rclone authorize output.', tooltipText: 'The drive ID appears in the JSON output of `rclone authorize "onedrive"` — look for the "drive_id" field in the response. It typically starts with "b!" for business/SharePoint drives. Personal OneDrive drive IDs look like a UUID.', }, { key: 'drive_type', label: 'Drive Type', inputType: 'select' as const, required: true, options: [ { value: 'personal', label: 'Personal' }, { value: 'business', label: 'Business' }, { value: 'documentLibrary', label: 'SharePoint Document Library' }, ], helpText: 'Personal for consumer OneDrive, Business for Microsoft 365', }, ], }, gdrive: { displayName: 'Google Drive', description: 'Google Drive (paste pre-obtained rclone token or service account)', category: 'cloud-drives' as const, fields: [ { key: 'token', label: 'OAuth Token (JSON)', inputType: 'password' as const, required: false, helpText: 'Paste the JSON token from: rclone authorize "drive". Use this OR a service account JSON, not both.', tooltipText: 'Run `rclone authorize "drive"` on a machine with a browser. A browser window opens, you sign in with Google, and rclone prints a JSON token — paste that entire JSON blob here.', }, { key: 'service_account_credentials', label: 'Service Account JSON', inputType: 'password' as const, required: false, helpText: 'For unattended/server use: paste the full service account JSON key file content. Use this OR an OAuth token, not both.', tooltipText: 'Create a service account at Google Cloud Console > IAM & Admin > Service Accounts, share the target Drive with that service account email, then download the JSON key and paste its contents here.', }, { key: 'root_folder_id', label: 'Root Folder ID', inputType: 'text' as const, required: false, helpText: 'Optional: restrict rclone to a specific folder. Leave blank to access the full Drive.', tooltipText: 'The folder ID is the last segment of the Google Drive folder URL: https://drive.google.com/drive/folders/{FOLDER_ID}', }, ], }, dropbox: { displayName: 'Dropbox', description: 'Dropbox (paste pre-obtained rclone token)', category: 'cloud-drives' as const, fields: [ { key: 'token', label: 'OAuth Token (JSON)', inputType: 'password' as const, required: true, helpText: 'Paste the JSON token from: rclone authorize "dropbox"', tooltipText: 'Run `rclone authorize "dropbox"` on a machine with a browser. Sign in to Dropbox when the browser opens, then paste the resulting JSON token here.', }, ], }, box: { displayName: 'Box', description: 'Box cloud storage (paste pre-obtained rclone token)', category: 'cloud-drives' as const, fields: [ { key: 'token', label: 'OAuth Token (JSON)', inputType: 'password' as const, required: true, helpText: 'Paste the JSON token from: rclone authorize "box"', tooltipText: 'Run `rclone authorize "box"` on a machine with a browser. Sign in to Box when the browser opens, then paste the resulting JSON token here.', }, { key: 'box_sub_type', label: 'Account Type', inputType: 'select' as const, required: false, options: [ { value: 'user', label: 'User account (default)' }, { value: 'enterprise', label: 'Enterprise / Service Account' }, ], helpText: 'Use Enterprise for Box App Users or service accounts', }, ], }, pcloud: { displayName: 'pCloud', description: 'pCloud cloud storage (paste pre-obtained rclone token)', category: 'cloud-drives' as const, fields: [ { key: 'token', label: 'OAuth Token (JSON)', inputType: 'password' as const, required: true, helpText: 'Paste the JSON token from: rclone authorize "pcloud"', tooltipText: 'Run `rclone authorize "pcloud"` on a machine with a browser. Sign in to pCloud when the browser opens, then paste the resulting JSON token here.', }, { key: 'hostname', label: 'Server Region', inputType: 'select' as const, required: false, options: [ { value: 'api.pcloud.com', label: 'US (default)' }, { value: 'eapi.pcloud.com', label: 'EU' }, ], helpText: 'Select EU if your pCloud account is registered in Europe', tooltipText: 'pCloud has separate server infrastructure for US and EU users. EU-registered accounts must use eapi.pcloud.com — using the wrong region will return authentication errors.', }, ], }, sftp: { displayName: 'SFTP', description: 'SSH File Transfer Protocol', category: 'protocol-based' as const, fields: [ { key: 'host', label: 'Host', inputType: 'text' as const, required: true, placeholder: 'sftp.example.com', helpText: 'The SSH server hostname or IP address', }, { key: 'user', label: 'Username', inputType: 'text' as const, required: true, placeholder: 'admin', }, { key: 'pass', label: 'Password', inputType: 'password' as const, required: false, helpText: 'SFTP password. Note: rclone may require the password to be obscured using `rclone obscure `. If authentication fails, use the obscured value instead of the plain password.', }, { key: 'key_pem', label: 'Private Key (PEM)', inputType: 'password' as const, required: false, helpText: 'Paste your private key in PEM format (-----BEGIN ... PRIVATE KEY-----)', }, ], }, ftp: { displayName: 'FTP', description: 'File Transfer Protocol (supports plain FTP and FTPS)', category: 'protocol-based' as const, fields: [ { key: 'host', label: 'Host', inputType: 'text' as const, required: true, placeholder: 'ftp.example.com', helpText: 'The FTP server hostname or IP address', }, { key: 'user', label: 'Username', inputType: 'text' as const, required: false, placeholder: 'anonymous', helpText: 'Leave blank or enter "anonymous" for anonymous FTP access', }, { key: 'pass', label: 'Password', inputType: 'password' as const, required: false, helpText: 'FTP password. Leave blank for anonymous access.', }, { key: 'port', label: 'Port', inputType: 'text' as const, required: false, placeholder: '21', helpText: 'FTP port (default: 21)', }, { key: 'explicit_tls', label: 'TLS Mode', inputType: 'select' as const, required: false, options: [ { value: '', label: 'Plain FTP (no encryption)' }, { value: 'true', label: 'Explicit FTPS (STARTTLS)' }, ], helpText: 'Use Explicit FTPS for encrypted FTP connections (port 21 + STARTTLS)', }, ], }, webdav: { displayName: 'WebDAV', description: 'WebDAV (NextCloud, SharePoint, OwnCloud, and others)', category: 'protocol-based' as const, fields: [ { key: 'url', label: 'WebDAV URL', inputType: 'text' as const, required: true, placeholder: 'https://example.com/dav', helpText: 'The full WebDAV endpoint URL', tooltipText: 'NextCloud: https://yourserver/remote.php/dav/files/username/ | OwnCloud: https://yourserver/remote.php/webdav/ | SharePoint: https://company.sharepoint.com/sites/mysite/Documents', }, { key: 'user', label: 'Username', inputType: 'text' as const, required: true, helpText: 'The WebDAV username', }, { key: 'pass', label: 'Password', inputType: 'password' as const, required: true, helpText: 'The WebDAV password', }, { key: 'vendor', label: 'Vendor / Server Type', inputType: 'select' as const, required: false, options: [ { value: 'nextcloud', label: 'NextCloud' }, { value: 'owncloud', label: 'OwnCloud' }, { value: 'sharepoint', label: 'SharePoint (Microsoft 365 OAuth)' }, { value: 'sharepoint-ntlm', label: 'SharePoint (NTLM / on-premises)' }, { value: 'other', label: 'Other WebDAV server' }, ], helpText: 'Selecting the correct vendor enables server-specific optimizations', }, ], }, smb: { displayName: 'SMB / Windows Share', description: 'SMB / CIFS Windows file shares', category: 'protocol-based' as const, fields: [ { key: 'host', label: 'Host', inputType: 'text' as const, required: true, placeholder: 'fileserver.example.com', helpText: 'The SMB server hostname or IP address', }, { key: 'user', label: 'Username', inputType: 'text' as const, required: true, helpText: 'The SMB username (can include domain: DOMAIN\\user)', }, { key: 'pass', label: 'Password', inputType: 'password' as const, required: false, helpText: 'The SMB password. Leave blank for guest/anonymous access.', }, { key: 'domain', label: 'Domain', inputType: 'text' as const, required: false, placeholder: 'WORKGROUP', helpText: 'Windows domain name (optional, defaults to WORKGROUP)', }, { key: 'port', label: 'Port', inputType: 'text' as const, required: false, placeholder: '445', helpText: 'SMB port (default: 445)', }, ], }, http: { displayName: 'HTTP (read-only)', description: 'Read-only access to HTTP/HTTPS file listings', category: 'protocol-based' as const, fields: [ { key: 'url', label: 'URL', inputType: 'text' as const, required: true, placeholder: 'https://example.com/path/', helpText: 'The base URL of the HTTP file listing. Must expose directory listings.', tooltipText: 'The HTTP backend is read-only and works with Apache/Nginx directory listings or any server that returns HTML with links. Ensure the URL ends with a trailing slash for directory access.', }, ], }, seafile: { displayName: 'Seafile', description: 'Seafile self-hosted cloud storage', category: 'protocol-based' as const, fields: [ { key: 'url', label: 'Server URL', inputType: 'text' as const, required: true, placeholder: 'https://cloud.seafile.com', helpText: 'The Seafile server URL (without trailing slash)', }, { key: 'user', label: 'Email / Username', inputType: 'text' as const, required: true, helpText: 'Your Seafile account email address', }, { key: 'pass', label: 'Password', inputType: 'password' as const, required: true, helpText: 'Your Seafile account password', }, ], }, } as const; // BackendType is derived from registry keys — no manual union needed. // Adding a backend to BACKEND_REGISTRY automatically extends BackendType. export type BackendType = keyof typeof BACKEND_REGISTRY;