From 4ee0f31bdf9c789e9f2000f0f51fd7697eee55ca Mon Sep 17 00:00:00 2001 From: MachinePainting Date: Sun, 1 Feb 2026 01:14:12 -0800 Subject: [PATCH] changes to setup workflow and instructions --- README.md | 546 +++++++++++-------------------------- drivesend_setup_node.py | 125 ++++++--- drivesend_uploader_node.py | 23 ++ gdrive_upload.py | 82 +++++- 4 files changed, 343 insertions(+), 433 deletions(-) diff --git a/README.md b/README.md index 6a72503..deddbca 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ # ComfyUI DriveSend Node -A ComfyUI custom node for seamless Google Drive uploads with **optional** encryption capabilities. Automatically upload your ComfyUI output files (images and videos) to Google Drive cloud storage — with or without encryption. +A ComfyUI custom node for seamless Google Drive uploads with **optional** encryption capabilities. Automatically upload your ComfyUI output files (images and videos) to Google Drive cloud storage. -> ⚠️ **Note:** This is a development/untested version based on the working [DropSend Node](https://github.com/machinepainting/ComfyUI_DropSendNode). Community testing welcome! +## ⚠️ Authentication Methods - READ FIRST + +| Method | Lifespan | Best For | Effort | +|--------|----------|----------|--------| +| **Service Account** | ✅ **Permanent** (never expires) | Cloud/RunPod | Medium setup | +| **OAuth 2.0** | ❌ **7 days** (must re-auth weekly) | Not recommended | Easy setup | + +**We strongly recommend Service Account** for any persistent use. OAuth tokens expire every 7 days in Google's "testing mode" and there is no workaround without paying for Google Workspace or going through Google's app verification process. + +--- ## 🔄 How It Works @@ -35,459 +44,216 @@ A ComfyUI custom node for seamless Google Drive uploads with **optional** encryp └─────────────────────────────────────────────────────────────────────────────┘ ``` -**Encryption is completely optional.** If you don't need it, simply leave `enable_encryption` off and your files upload directly to Google Drive as-is. Enable encryption only if you want an extra layer of security for your files in cloud storage. - -## 📤📦 Features - -- **📤📦 DriveSend AutoUploader Node** - Automatically uploads newly created files to Google Drive with optional file encryption capabilities. - - - Monitors a specified folder (e.g., ComfyUI's `output/`) in real time, with optional recursive subfolder monitoring. - - Supports common ComfyUI file types: `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.mp4`, `.avi`, `.mov`. - - Optional encryption of files before upload, creating `.enc` files using a secure Fernet key (AES-128). - - Configurable toggles for: - - `enable_encryption`: Encrypt files before upload (default: off). - - `Post_Delete_Enc`: Delete encrypted `.enc` files after upload verification (default: off). - - `Subfolder_Monitor`: Monitor subfolders in the watch directory (default: on). - - `run_process`: Start or stop the monitoring and uploading process (default: on). - - Uses a queue system to ensure reliable processing of files, even under high load, preventing skipped files. - - Verifies upload integrity using SHA256 checksums to ensure files are not corrupted during transfer. - -- **🛠️📦 DriveSend Setup Node** - Streamlines Google Drive API access setup and encryption key management. - - - Supports two authentication methods: - - **Service Account** (Recommended for cloud/RunPod): No browser interaction needed after initial setup. - - **OAuth 2.0**: For local setups where you want to use your personal Google account. - - Provides API credentials and optional encryption key for easy integration into Environment Variables and RunPod Secrets. - - Supports two storage methods: - - `env_file`: Saves credentials to a `.env` file (recommended for local user setups). - - `display_only`: Displays credentials in the console for manual copying (recommended for cloud setups like RunPod). - - Supports three encryption key methods: - - `off`: No encryption key is generated (use if encryption is not needed). - - `Display Only`: Displays the encryption key in the console with other credentials. - - `save to .env`: Saves the encryption key to the `.env` file. - -- **🔐📁 Standalone Decryption Scripts (Local Use Only)** - Decrypt `.enc` files on your local machine using the included scripts in the `/scripts/` folder. - - - **Local use only** — Run these on your computer after downloading/syncing encrypted files from Google Drive. - - Cross-platform support for macOS, Windows, and Linux. - - Restores encrypted files back to their original format (PNG, JPG, MP4, etc.). - - Supports recursive folder processing. - - Option to organize `.enc` files after decryption. - - Includes optional encryption scripts for manual local encryption (not needed for normal DriveSend operation). - --- -## 💾📦 Installation - -Clone this repository into the `ComfyUI/custom_nodes/` directory: +## 💾 Installation ```bash cd ComfyUI/custom_nodes/ git clone https://github.com/machinepainting/ComfyUI_DriveSendNode.git -``` - -Install dependencies: - -```bash +cd ComfyUI_DriveSendNode pip install -r requirements.txt ``` --- -## 🔧📦 Google Drive Setup Instructions +## 🔧 Google Cloud Setup (Service Account - Recommended) -### Option A: Service Account (Recommended for Cloud/RunPod) +### Step 1: Create Google Cloud Project -Service accounts allow server-to-server authentication without browser interaction — ideal for cloud deployments. +1. Go to [Google Cloud Console](https://console.cloud.google.com/) +2. Sign in with a **personal Gmail account** (not work/school - they often have restrictions) +3. Click **Select a project** → **New Project** +4. Name it (e.g., `ComfyUI-DriveSend`) → **Create** -1. Go to the [Google Cloud Console](https://console.cloud.google.com/) -2. Create a new project (or select an existing one) -3. Enable the Google Drive API: - - Navigate to **APIs & Services > Library** - - Search for "Google Drive API" - - Click **Enable** +### Step 2: Enable Google Drive API -4. Create a Service Account: - - Navigate to **IAM & Admin > Service Accounts** - - Click **Create Service Account** - - Enter a name (e.g., `comfyui-drivesend`) - - Click **Create and Continue** - - Skip the optional steps, click **Done** +1. Go to **APIs & Services** → **Library** +2. Search for **Google Drive API** +3. Click **Enable** -5. Create and Download the Key: - - Click on your newly created service account - - Go to the **Keys** tab - - Click **Add Key > Create new key** - - Select **JSON** and click **Create** - - Save the downloaded file as `service_account.json` +### Step 3: Create Service Account -6. Share Your Google Drive Folder: - - Open Google Drive and create a folder for uploads (e.g., `ComfyUI_Output`) - - Right-click the folder > **Share** - - Add the service account email (found in `service_account.json` as `client_email`) - - Give it **Editor** access - - Copy the **Folder ID** from the URL (the long string after `/folders/`) +1. Go to **IAM & Admin** → **Service Accounts** +2. Click **+ Create Service Account** +3. Name it (e.g., `comfyui-uploader`) +4. Click **Create and Continue** +5. Skip optional steps → **Done** -### Option B: OAuth 2.0 (For Local/Personal Use) +### Step 4: Fix Organization Policy (IMPORTANT - New Google Accounts) -OAuth allows you to upload to your personal Google Drive account. +Google now blocks service account key creation by default. You MUST disable this policy first. -1. Go to the [Google Cloud Console](https://console.cloud.google.com/) -2. Create a new project (or select an existing one) -3. Enable the Google Drive API (same as above) +#### 4a. Grant Yourself Policy Admin Role -4. Configure OAuth Consent Screen: - - Navigate to **APIs & Services > OAuth consent screen** - - Select **External** (unless you have Google Workspace) - - Fill in the required fields (App name, support email) - - Add your email as a test user - - Save +1. Go to **IAM & Admin** → **IAM** +2. Click the dropdown at top-left and select your **organization** (your email domain), not the project +3. Click **+ Grant Access** +4. Principal: **your email** +5. Role: search for **Organization Policy Administrator** +6. Click **Save** -5. Create OAuth Credentials: - - Navigate to **APIs & Services > Credentials** - - Click **Create Credentials > OAuth client ID** - - Select **Desktop app** - - Download the JSON file as `credentials.json` +#### 4b. Enable the Organization Policy API ---- - -## 🏃‍♂️‍➡️🫛📦 DriveSend Setup Node Instructions (RunPod & Cloud Users) - -Using Service Account authentication (recommended for cloud): - -1. Upload your `service_account.json` to a secure location on your pod, or encode it as base64 and store as an environment variable. - -2. Open the 'DriveSend Setup Node' in your ComfyUI and configure: - - - `auth_method` [ service_account ] - - `service_account_json` [ path to service_account.json OR leave blank if using env var ] - - `folder_id` [ paste your Google Drive folder ID ] - - `storage_method` [ display_only ] - - `encryption_key_method` [ Display Only ] (or off if not needed) - -3. Click 'Run' on the node. Note the returned credentials in the terminal: +Open **Cloud Shell** (terminal icon at top-right) and run: ```bash -GOOGLE_DRIVE_FOLDER_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -GOOGLE_SERVICE_ACCOUNT_JSON= -comfyui_encryption_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (if encryption enabled) +gcloud services enable orgpolicy.googleapis.com --project=YOUR_PROJECT_ID ``` -4. For RunPod users, create secrets: +Replace `YOUR_PROJECT_ID` with your actual project ID. - | Secret Name | Secret Value | - |-------------|--------------| - | `GOOGLE_DRIVE_FOLDER_ID` | Your folder ID | - | `GOOGLE_SERVICE_ACCOUNT_JSON` | Base64-encoded service account JSON | - | `comfyui_encryption_key` | Your encryption key (if using encryption) | +#### 4c. Disable the Key Creation Restrictions -5. Add Environment Variables to your RunPod Pod: - - `GOOGLE_DRIVE_FOLDER_ID` → `{{ RUNPOD_SECRET_GOOGLE_DRIVE_FOLDER_ID }}` - - `GOOGLE_SERVICE_ACCOUNT_JSON` → `{{ RUNPOD_SECRET_GOOGLE_SERVICE_ACCOUNT_JSON }}` - - `comfyui_encryption_key` → `{{ RUNPOD_SECRET_comfyui_encryption_key }}` - -6. Add the DriveSend AutoUploader Node to your workflow and run! - ---- - -## 🛠️💻📦 DriveSend Setup Node Instructions (Local Computer Users) - -Using OAuth 2.0 authentication: - -1. Place your `credentials.json` in the `ComfyUI_DriveSendNode` folder - -2. Open the 'DriveSend Setup Node' in your ComfyUI and configure: - - - `auth_method` [ oauth ] - - `folder_id` [ paste your Google Drive folder ID ] (optional - uploads to root if blank) - - `storage_method` [ env_file ] - - `encryption_key_method` [ save to .env ] (or off if not needed) - -3. Click 'Run' on the node. A browser window will open for Google authentication. - -4. After authenticating, a `token.json` file will be created automatically. - -5. Restart ComfyUI and add the DriveSend AutoUploader Node to your workflow. - ---- - -## 🔑📦 Encryption Key Management - -If you enable encryption in the DriveSend AutoUploader Node, an encryption key is generated during setup (unless `encryption_key_method` is `off`). This key is **required** to decrypt `.enc` files downloaded from Google Drive. - -### Saving the Encryption Key - -**Display Only (Recommended for Cloud):** - -If `encryption_key_method` is `Display Only`, the key is shown in the console: - -``` -comfyui_encryption_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -``` - -Copy the key and store it securely using one of the methods below. - -**Save to .env (Recommended for Local):** - -If `encryption_key_method` is `save to .env`, the key is saved in `ComfyUI/custom_nodes/ComfyUI_DriveSendNode/.env` as: - -``` -comfyui_encryption_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -``` - -Ensure the `.env` file is excluded from version control (add to `.gitignore`). - ---- - -## 🔐📁 Standalone Decryption Scripts (Local Use Only) - -The `/scripts/` folder contains standalone scripts to decrypt files on your **local machine**. - -> ⚠️ **These scripts are for LOCAL USE ONLY.** Run them on your personal computer after downloading or syncing encrypted files from Google Drive. Do not run on cloud instances. - -> 💡 **Universal Compatibility:** These scripts work with both DriveSend (Google Drive) and DropSend (Dropbox) nodes. They use the same encryption key (`comfyui_encryption_key`), so you only need to set up key storage once. - -### What Are These Scripts For? - -**Decryption Scripts** — The primary scripts. Use these on your local machine to decrypt `.enc` files you've downloaded/synced from Google Drive. When encryption is enabled in the DriveSend node, your files are uploaded as encrypted `.enc` files. These scripts restore them to their original format so you can view and use them. - -**Encryption Scripts** — Optional utility scripts. You do NOT need these for normal DriveSend operation—the node handles encryption automatically during upload. These are provided for users who want to manually encrypt local files for backup or other purposes using the same key. - -### Supported File Types - -The scripts support all formats the DriveSend node handles: - -- Images: `.png`, `.jpg`, `.jpeg`, `.webp`, `.gif` -- Videos: `.mp4`, `.avi`, `.mov` - -When decrypting, the original file extension is preserved (e.g., `video.mp4.enc` → `video.mp4`). - -### Prerequisites (All Platforms) - -**Python 3.8+** and the **cryptography** library are required: +Run these commands in Cloud Shell: ```bash -pip install cryptography +gcloud org-policies reset iam.disableServiceAccountKeyCreation --project=YOUR_PROJECT_ID + +gcloud org-policies reset iam.managed.disableServiceAccountKeyCreation --project=YOUR_PROJECT_ID ``` ---- +### Step 5: Create and Download the Key -## 🍎 macOS Setup & Usage +1. Switch back to your **project** (click dropdown top-left, select your project) +2. Go to **IAM & Admin** → **Service Accounts** +3. Click on your service account +4. Go to **Keys** tab +5. Click **Add Key** → **Create new key** → **JSON** → **Create** +6. **IMPORTANT:** Rename the downloaded file to exactly: `service_account.json` -### Storing Your Encryption Key in Keychain +### Step 6: Share Your Google Drive Folder -1. Open **Keychain Access** (search in Spotlight) -2. Click **File > New Password Item** -3. Fill in the fields: - - **Keychain Item Name:** `ComfyUI_Encryption_Key` - - **Account Name:** `ComfyUI` - - **Password:** Paste your encryption key -4. Click **Add** +1. Go to [Google Drive](https://drive.google.com) +2. Create a new folder (e.g., `ComfyUI_Uploads`) +3. Right-click the folder → **Share** +4. Open your `service_account.json` file and find the `client_email` value (looks like `name@project-id.iam.gserviceaccount.com`) +5. Paste that email in the Share dialog +6. Set permission to **Editor** +7. Click **Share** (uncheck "Notify people" if prompted) -To retrieve later: Search for `ComfyUI_Encryption_Key` in Keychain Access, double-click, check **Show Password**, and authenticate. +### Step 7: Get Your Folder ID -### Running the Scripts - -1. Navigate to the scripts folder: - ```bash - cd ComfyUI/custom_nodes/ComfyUI_DriveSendNode/scripts - ``` - -2. Make the script executable (first time only): - ```bash - chmod +x decrypt_folder_mac.sh - chmod +x encrypt_folder_mac.sh - ``` - -3. Run the decryption script: - ```bash - ./decrypt_folder_mac.sh - ``` - -4. When prompted: - - Drag and drop the folder containing `.enc` files into the terminal, or type the path - - Choose whether to process subfolders recursively (Y/N) - - Optionally move `.enc` files to a separate folder after decryption +1. Open the folder in Google Drive +2. Look at the URL: `https://drive.google.com/drive/folders/XXXXXXXXXXXXXXXXX` +3. Copy the long string after `/folders/` — that's your **Folder ID** --- -## 🪟 Windows Setup & Usage +## 🚀 Local Setup (Quick Test) -### Storing Your Encryption Key - -**Using Environment Variable (Recommended):** - -1. Press `Win + R`, type `sysdm.cpl`, press Enter -2. Go to **Advanced** tab → **Environment Variables** -3. Under **User variables**, click **New** -4. Set: - - **Variable name:** `COMFYUI_ENCRYPTION_KEY` - - **Variable value:** Your encryption key -5. Click **OK** to save -6. Restart any open terminals/command prompts - -### Running the Scripts - -1. Open **Command Prompt** or **PowerShell** - -2. Navigate to the scripts folder: - ```cmd - cd ComfyUI\custom_nodes\ComfyUI_DriveSendNode\scripts - ``` - -3. Run the decryption script: - ```cmd - python decrypt_folder_win.py - ``` - -4. When prompted: - - Enter the full path to the folder containing `.enc` files - - Choose whether to process subfolders recursively (Y/N) - - Optionally move `.enc` files to a separate folder after decryption +1. Place `service_account.json` in the `ComfyUI_DriveSendNode` folder +2. Add the **DriveSend Setup** node to ComfyUI +3. Configure: + - `auth_method`: **service_account** + - `folder_id`: your folder ID + - `owner_email`: **your Gmail address** (required!) + - `storage_method`: **env_file** + - `encryption_key_method`: **off** (for testing) +4. Run the node +5. Add **DriveSend AutoUploader** node and run a workflow --- -## 🐧 Linux Setup & Usage +## ☁️ RunPod / Cloud Setup (Persistent) -### Storing Your Encryption Key +### Option A: Custom Template (Recommended - One-Time Setup) -**Option A: Environment Variable (Recommended)** +1. Run the **DriveSend Setup** node locally with `storage_method`: **display_only** +2. Copy the output values from the console +3. In RunPod, go to **Secrets** and create: -Add to your `~/.bashrc` or `~/.zshrc`: +| Secret Name | Value | +|-------------|-------| +| `GOOGLE_DRIVE_FOLDER_ID` | Your folder ID | +| `GOOGLE_SERVICE_ACCOUNT_JSON` | The base64 string from setup | +| `GOOGLE_OWNER_EMAIL` | Your Gmail address | +| `comfyui_encryption_key` | (only if using encryption) | -```bash -export COMFYUI_ENCRYPTION_KEY="your_encryption_key_here" -``` +4. Create or edit a **Pod Template**: + - Click **Edit Template** → **Environment Variables** + - Add each variable, linking to secrets: + - Key: `GOOGLE_DRIVE_FOLDER_ID` → Value: `{{ RUNPOD_SECRET_GOOGLE_DRIVE_FOLDER_ID }}` + - Key: `GOOGLE_SERVICE_ACCOUNT_JSON` → Value: `{{ RUNPOD_SECRET_GOOGLE_SERVICE_ACCOUNT_JSON }}` + - Key: `GOOGLE_OWNER_EMAIL` → Value: `{{ RUNPOD_SECRET_GOOGLE_OWNER_EMAIL }}` + - Key: `comfyui_encryption_key` → Value: `{{ RUNPOD_SECRET_comfyui_encryption_key }}` + - Click **Set Overrides** -Then reload: +5. Deploy pods using this template — credentials persist automatically! -```bash -source ~/.bashrc -``` +### Option B: Manual Environment Variables (Each Pod) -**Option B: Secret Service (GNOME Keyring / KWallet)** - -If you have `secret-tool` installed (comes with `libsecret-tools`): - -```bash -# Store the key -echo -n "your_encryption_key_here" | secret-tool store --label="ComfyUI Encryption Key" service ComfyUI username ComfyUI - -# Retrieve the key (for verification) -secret-tool lookup service ComfyUI username ComfyUI -``` - -Install secret-tool if needed: - -```bash -# Debian/Ubuntu -sudo apt install libsecret-tools - -# Fedora -sudo dnf install libsecret - -# Arch -sudo pacman -S libsecret -``` - -### Running the Scripts - -1. Navigate to the scripts folder: - ```bash - cd ComfyUI/custom_nodes/ComfyUI_DriveSendNode/scripts - ``` - -2. Make the script executable (first time only): - ```bash - chmod +x decrypt_folder_linux.sh - chmod +x encrypt_folder_linux.sh - ``` - -3. Run the decryption script: - ```bash - ./decrypt_folder_linux.sh - ``` - -4. When prompted: - - Enter the full path to the folder containing `.enc` files - - Choose whether to process subfolders recursively (Y/N) - - Optionally move `.enc` files to a separate folder after decryption +If you don't want to create a template, manually add the environment variables each time you create a pod. This is more tedious but works the same way. --- -## 🔄 Cross-Platform Python Script (Alternative) +## 📋 Node Settings Reference -For maximum compatibility, use the Python script directly on any platform: +### DriveSend Setup Node -```bash -cd ComfyUI/custom_nodes/ComfyUI_DriveSendNode/scripts -python decrypt_folder.py -``` +| Field | Description | +|-------|-------------| +| `auth_method` | `service_account` (recommended) or `oauth` | +| `folder_id` | Google Drive folder ID from URL | +| `owner_email` | **Your Gmail** - required for service account! | +| `storage_method` | `display_only` (cloud) or `env_file` (local) | +| `encryption_key_method` | `off`, `Display Only`, or `save to .env` | +| `service_account_path` | Default: `service_account.json` | -This script will: +### DriveSend AutoUploader Node -1. Automatically detect your operating system -2. Check for your encryption key (environment variable, Keychain, or Secret Service) -3. Prompt for the key if not found -4. Process all `.enc` files and restore them to their original format +| Field | Description | +|-------|-------------| +| `watch_directory` | Folder to monitor (default: ComfyUI output) | +| `auth_method` | Must match setup node | +| `folder_id` | Override folder ID (or leave blank to use env var) | +| `owner_email` | Override owner email (or leave blank to use env var) | +| `enable_encryption` | Encrypt files before upload | +| `Post_Delete_Enc` | Delete .enc files after upload | +| `Subfolder_Monitor` | Watch subfolders too | +| `run_process` | Start/stop the monitor | --- -## ⚠️ Security Best Practices +## ❓ Why is `owner_email` Required? -1. **Never commit your `.env` file** - Ensure `.env` is in your `.gitignore` -2. **Never commit `service_account.json` or `credentials.json`** - These contain sensitive credentials -3. **Never commit `token.json`** - This contains your OAuth tokens -4. **Use secure key storage** - Prefer OS-native credential storage over plain text files -5. **Backup your encryption key** - Without it, encrypted files cannot be recovered -6. **Restrict service account permissions** - Only share specific folders, not your entire Drive +**Service accounts have 0 GB storage quota.** When a service account uploads a file, it owns that file — but it has no storage space! + +The `owner_email` setting transfers ownership to your personal Gmail account after upload, so the file uses YOUR storage quota (15 GB free). + +Without this, uploads will fail with: `403 storageQuotaExceeded` --- -## ⚠️ Important Notes +## 🔐 Encryption (Optional) -### Using Both DriveSend and DropSend +Enable encryption to protect files in cloud storage: -If you use both nodes (DriveSend for Google Drive and [DropSend](https://github.com/machinepainting/ComfyUI_DropSendNode) for Dropbox), you only need to store your encryption key **once** using any of the methods above. The scripts check for multiple key names for backward compatibility: +1. In Setup node: set `encryption_key_method` to **Display Only** or **save to .env** +2. In AutoUploader: set `enable_encryption` to **True** +3. Save your encryption key securely — you need it to decrypt files! -- `COMFYUI_ENCRYPTION_KEY` (recommended) -- `comfyui_encryption_key` -- `DRIVESEND_ENCRYPTION_KEY` (legacy) -- `DROPSEND_ENCRYPTION_KEY` (legacy) - -The same applies to Keychain/Secret Service - the scripts will find your key regardless of which name you used. - -### Running Both Nodes Simultaneously - -If you have both DriveSend and DropSend installed and want to use them at the same time, configure them to watch **different folders** to avoid conflicts. For example: - -- DriveSend watches: `ComfyUI/output/gdrive/` -- DropSend watches: `ComfyUI/output/dropbox/` - -Or simply use one node at a time by setting `run_process` to `False` on the node you're not using. +See the `/scripts/` folder for decryption scripts (run on your local machine after downloading). --- -## 🧪 Tested On +## 🛠️ Troubleshooting -**NOT Tested:** -This is a development version based on the working DropSend Node. Community testing welcome! +### "service_account.json NOT FOUND" +- Rename your downloaded key file to exactly `service_account.json` +- Place it in the `ComfyUI_DriveSendNode` folder -**Community Testing Needed:** -- macOS — *Please test and report any issues or suggestions!* -- Windows 10/11 — *Please test and report any issues or suggestions!* -- Linux (Ubuntu, Fedora, Arch) — *Please test and report any issues or suggestions!* +### "403 storageQuotaExceeded" +- Set `owner_email` to your Gmail address +- Make sure the folder is shared with your service account email -If you encounter any problems, please open an issue on GitHub with: -- Your OS version -- Python version -- Error messages (if any) -- Steps to reproduce +### "Organization Policy blocks key creation" +- Follow Step 4 in the setup guide to disable the policy restrictions +- Run the gcloud commands in Cloud Shell -Contributions and pull requests are welcome! +### "Permission denied" on upload +- Make sure you shared the Google Drive folder with the service account email +- The service account needs **Editor** access --- @@ -506,17 +272,25 @@ ComfyUI_DriveSendNode/ ├── README.md ├── .gitignore └── scripts/ - ├── decrypt_folder.py # Cross-platform Python script (recommended) - ├── decrypt_folder_mac.sh # macOS decryption script - ├── encrypt_folder_mac.sh # macOS encryption script (local use only) - ├── decrypt_folder_win.py # Windows decryption script - ├── encrypt_folder_win.py # Windows encryption script (local use only) - ├── decrypt_folder_linux.sh # Linux decryption script - └── encrypt_folder_linux.sh # Linux encryption script (local use only) + ├── decrypt_folder.py + ├── decrypt_folder_mac.sh + ├── encrypt_folder_mac.sh + ├── decrypt_folder_win.py + ├── encrypt_folder_win.py + ├── decrypt_folder_linux.sh + └── encrypt_folder_linux.sh ``` --- +## 🧪 Status + +**Testing in Progress** — Based on the working [DropSend Node](https://github.com/machinepainting/ComfyUI_DropSendNode). + +Please report issues on GitHub! + +--- + ## License MIT diff --git a/drivesend_setup_node.py b/drivesend_setup_node.py index 2e5fb9c..3f21ecc 100644 --- a/drivesend_setup_node.py +++ b/drivesend_setup_node.py @@ -29,14 +29,19 @@ class DriveSendSetupNode: "multiline": False, "placeholder": "Google Drive Folder ID (from URL)" }), - "storage_method": (["env_file", "display_only"],), + "owner_email": ("STRING", { + "default": "", + "multiline": False, + "placeholder": "Your Gmail address (for ownership transfer)" + }), + "storage_method": (["display_only", "env_file"],), "encryption_key_method": (["off", "Display Only", "save to .env"],), }, "optional": { "service_account_path": ("STRING", { - "default": "", + "default": "service_account.json", "multiline": False, - "placeholder": "Path to service_account.json (optional)" + "placeholder": "Path to service_account.json" }), "reconnect": ("BOOLEAN", {"default": False}), } @@ -46,32 +51,56 @@ class DriveSendSetupNode: RETURN_NAMES = ("status",) FUNCTION = "setup" CATEGORY = "DriveSend" + OUTPUT_NODE = True def setup( self, auth_method, folder_id, + owner_email, storage_method, encryption_key_method, - service_account_path="", + service_account_path="service_account.json", reconnect=False ): output_lines = [] env_vars = {} + print("\n" + "="*60) + print("[DriveSend Setup] Starting configuration...") + print("="*60) + # Handle folder ID if folder_id: env_vars['GOOGLE_DRIVE_FOLDER_ID'] = folder_id - output_lines.append(f"GOOGLE_DRIVE_FOLDER_ID={folder_id}") + output_lines.append(f"✓ Folder ID configured") + else: + output_lines.append("⚠ No Folder ID provided - will upload to Drive root") + + # Handle owner email (required for service account) + if owner_email: + env_vars['GOOGLE_OWNER_EMAIL'] = owner_email + output_lines.append(f"✓ Owner Email: {owner_email}") + elif auth_method == "service_account": + output_lines.append("⚠ WARNING: No owner_email set!") + output_lines.append(" Service accounts have 0 GB storage quota.") + output_lines.append(" Files will fail to upload without ownership transfer.") # Handle authentication based on method if auth_method == "service_account": - # Try to find and encode service account + # Try to find service account file sa_path = None - if service_account_path and Path(service_account_path).exists(): - sa_path = Path(service_account_path) - elif (NODE_DIR / 'service_account.json').exists(): + # Check provided path first + if service_account_path: + check_path = Path(service_account_path) + if check_path.exists(): + sa_path = check_path + elif (NODE_DIR / service_account_path).exists(): + sa_path = NODE_DIR / service_account_path + + # Fall back to default name in node directory + if not sa_path and (NODE_DIR / 'service_account.json').exists(): sa_path = NODE_DIR / 'service_account.json' if sa_path: @@ -87,57 +116,65 @@ class DriveSendSetupNode: sa_data = json.loads(sa_json) client_email = sa_data.get('client_email', 'unknown') - output_lines.append(f"Service Account: {client_email}") - output_lines.append(f"GOOGLE_SERVICE_ACCOUNT_JSON=") + output_lines.append(f"✓ Service Account loaded: {client_email}") + output_lines.append(f" Make sure your Drive folder is shared with this email!") - print(f"\n[DriveSend Setup] Service Account loaded: {client_email}") - print(f"[DriveSend Setup] Make sure to share your Google Drive folder with: {client_email}") + print(f"\n[DriveSend Setup] Service Account: {client_email}") + print(f"[DriveSend Setup] Share your Google Drive folder with: {client_email}") except Exception as e: - output_lines.append(f"Error loading service account: {e}") + output_lines.append(f"✗ Error loading service account: {e}") else: - output_lines.append("Warning: No service_account.json found") - output_lines.append("Please place service_account.json in the node directory or provide a path") + output_lines.append("✗ service_account.json NOT FOUND") + output_lines.append("") + output_lines.append("Please rename your downloaded JSON key file to:") + output_lines.append(" service_account.json") + output_lines.append("") + output_lines.append("And place it in:") + output_lines.append(f" {NODE_DIR}/") + return ("\n".join(output_lines),) elif auth_method == "oauth": - # OAuth will be handled at runtime + output_lines.append("") + output_lines.append("⚠ OAuth WARNING:") + output_lines.append(" OAuth tokens expire every 7 DAYS in testing mode.") + output_lines.append(" You will need to re-authenticate weekly.") + output_lines.append(" Service Account is recommended for persistent use.") + output_lines.append("") + creds_path = NODE_DIR / 'credentials.json' token_path = NODE_DIR / 'token.json' if creds_path.exists(): - output_lines.append("OAuth credentials.json found") + output_lines.append("✓ OAuth credentials.json found") if token_path.exists() and not reconnect: - output_lines.append("Existing token.json found - already authenticated") + output_lines.append("✓ Existing token.json found - already authenticated") else: - output_lines.append("Run the AutoUploader node to complete OAuth authentication") + output_lines.append("→ Run the AutoUploader node to complete OAuth authentication") if reconnect: - # Delete existing token to force re-auth try: token_path.unlink() - output_lines.append("Deleted existing token for reconnection") + output_lines.append(" Deleted existing token for reconnection") except: pass else: - output_lines.append("Warning: credentials.json not found") - output_lines.append("Please download OAuth credentials from Google Cloud Console") + output_lines.append("✗ credentials.json NOT FOUND") + output_lines.append(" Download OAuth credentials from Google Cloud Console") # Handle encryption key if encryption_key_method != "off": - # Check if key already exists existing_key = os.environ.get('comfyui_encryption_key') if existing_key and not reconnect: - output_lines.append("Encryption key already configured") + output_lines.append("✓ Encryption key already configured") env_vars['comfyui_encryption_key'] = existing_key else: - # Generate new key new_key = generate_key() env_vars['comfyui_encryption_key'] = new_key - output_lines.append(f"comfyui_encryption_key={new_key}") - print(f"\n[DriveSend Setup] Generated encryption key: {new_key}") + output_lines.append("✓ New encryption key generated") - # Save to .env file if requested + # Output based on storage method if storage_method == "env_file" and env_vars: env_path = NODE_DIR / '.env' @@ -159,22 +196,30 @@ class DriveSendSetupNode: for key, value in existing_vars.items(): f.write(f"{key}={value}\n") - output_lines.append(f"\nCredentials saved to: {env_path}") - print(f"[DriveSend Setup] Credentials saved to {env_path}") + output_lines.append(f"\n✓ Credentials saved to: {env_path}") elif storage_method == "display_only": - output_lines.append("\n--- Copy these to your environment variables ---") + output_lines.append("") + output_lines.append("="*50) + output_lines.append("COPY THESE TO RUNPOD SECRETS / ENV VARIABLES:") + output_lines.append("="*50) + + # Print to console for copying + print("\n" + "="*60) + print("COPY THESE VALUES TO YOUR ENVIRONMENT VARIABLES:") + print("="*60) + for key, value in env_vars.items(): - if key == 'GOOGLE_SERVICE_ACCOUNT_JSON': - # Print full base64 for copying - print(f"\n{key}={value}") - else: - print(f"{key}={value}") + print(f"\n{key}={value}") + + print("\n" + "="*60) + print("See console output above for full values to copy") + print("="*60) # Print summary - print("\n" + "="*50) + print("\n" + "="*60) print("[DriveSend Setup] Configuration complete!") - print("="*50) + print("="*60 + "\n") status = "\n".join(output_lines) return (status,) diff --git a/drivesend_uploader_node.py b/drivesend_uploader_node.py index dccab18..6013f22 100644 --- a/drivesend_uploader_node.py +++ b/drivesend_uploader_node.py @@ -36,6 +36,11 @@ class DriveSendAutoUploaderNode: "multiline": False, "placeholder": "Google Drive Folder ID (uses env var if blank)" }), + "owner_email": ("STRING", { + "default": "", + "multiline": False, + "placeholder": "Your Gmail address (required for service account)" + }), "enable_encryption": ("BOOLEAN", {"default": False}), "Post_Delete_Enc": ("BOOLEAN", {"default": False}), "Subfolder_Monitor": ("BOOLEAN", {"default": True}), @@ -54,6 +59,7 @@ class DriveSendAutoUploaderNode: auth_method, run_process, folder_id="", + owner_email="", enable_encryption=False, Post_Delete_Enc=False, Subfolder_Monitor=True, @@ -80,6 +86,20 @@ class DriveSendAutoUploaderNode: status_lines.append(f"Error: Could not create watch directory: {e}") return ("\n".join(status_lines), any_input) + # Get owner email from field or environment + effective_owner_email = owner_email if owner_email else os.environ.get('GOOGLE_OWNER_EMAIL', '') + + # Warn if using service account without owner email + if auth_method == 'service_account' and not effective_owner_email: + status_lines.append("⚠ WARNING: No owner_email set!") + status_lines.append(" Service accounts have 0 GB storage quota.") + status_lines.append(" Set owner_email to your Gmail or set GOOGLE_OWNER_EMAIL env var.") + status_lines.append("") + + # Set owner email in environment for upload module + if effective_owner_email: + os.environ['GOOGLE_OWNER_EMAIL'] = effective_owner_email + # Start monitor try: monitor = start_monitor( @@ -97,6 +117,9 @@ class DriveSendAutoUploaderNode: status_lines.append(f" Recursive: {Subfolder_Monitor}") status_lines.append(f" Encryption: {enable_encryption}") + if effective_owner_email: + status_lines.append(f" Owner Email: {effective_owner_email}") + if folder_id: status_lines.append(f" Folder ID: {folder_id}") else: diff --git a/gdrive_upload.py b/gdrive_upload.py index ce53337..9803258 100644 --- a/gdrive_upload.py +++ b/gdrive_upload.py @@ -1,6 +1,6 @@ """ Google Drive Upload Module -Handles file uploads to Google Drive with integrity verification +Handles file uploads to Google Drive with integrity verification and ownership transfer """ import os @@ -42,7 +42,52 @@ def calculate_sha256(file_path): return sha256_hash.hexdigest() -def upload_file(file_path, folder_id=None, service=None, auth_method='service_account', **auth_kwargs): +def transfer_ownership(service, file_id, owner_email): + """ + Transfer file ownership to a personal Google account. + + This is required because service accounts have 0 GB storage quota. + By transferring ownership, the file counts against the personal account's quota. + + Args: + service: Google Drive API service object + file_id: ID of the uploaded file + owner_email: Email address to transfer ownership to + + Returns: + bool: True if transfer successful, False otherwise + """ + if not owner_email: + return False + + try: + # Create permission with ownership transfer + permission = { + 'type': 'user', + 'role': 'owner', + 'emailAddress': owner_email + } + + service.permissions().create( + fileId=file_id, + body=permission, + transferOwnership=True, + supportsAllDrives=True + ).execute() + + print(f"[DriveSend] ✓ Ownership transferred to: {owner_email}") + return True + + except HttpError as e: + print(f"[DriveSend] ⚠ Could not transfer ownership: {e}") + return False + except Exception as e: + print(f"[DriveSend] ⚠ Ownership transfer error: {e}") + return False + + +def upload_file(file_path, folder_id=None, service=None, auth_method='service_account', + owner_email=None, **auth_kwargs): """ Upload a file to Google Drive. @@ -51,6 +96,7 @@ def upload_file(file_path, folder_id=None, service=None, auth_method='service_ac folder_id: Google Drive folder ID (optional, uses env var or root if not provided) service: Existing Drive service object (optional) auth_method: Authentication method ('service_account' or 'oauth') + owner_email: Email to transfer ownership to (required for service account uploads) **auth_kwargs: Additional auth arguments Returns: @@ -72,6 +118,10 @@ def upload_file(file_path, folder_id=None, service=None, auth_method='service_ac if folder_id is None: folder_id = get_folder_id() + # Get owner email from parameter or environment + if owner_email is None: + owner_email = os.environ.get('GOOGLE_OWNER_EMAIL') + # Calculate local hash for verification local_hash = calculate_sha256(file_path) @@ -99,13 +149,26 @@ def upload_file(file_path, folder_id=None, service=None, auth_method='service_ac file = service.files().create( body=file_metadata, media_body=media, - fields='id, name, size, md5Checksum' + fields='id, name, size, md5Checksum', + supportsAllDrives=True ).execute() file_id = file.get('id') remote_size = int(file.get('size', 0)) local_size = file_path.stat().st_size + # Transfer ownership if using service account + ownership_transferred = False + if auth_method == 'service_account' and owner_email: + ownership_transferred = transfer_ownership(service, file_id, owner_email) + if not ownership_transferred: + print(f"[DriveSend] ⚠ Warning: File uploaded but ownership not transferred.") + print(f"[DriveSend] The file may not persist if service account quota is exceeded.") + print(f"[DriveSend] Make sure GOOGLE_OWNER_EMAIL is set to your personal Gmail.") + elif auth_method == 'service_account' and not owner_email: + print(f"[DriveSend] ⚠ Warning: No owner_email set. Service accounts have 0 GB quota.") + print(f"[DriveSend] Set GOOGLE_OWNER_EMAIL environment variable to your Gmail address.") + # Verify upload size_match = remote_size == local_size @@ -117,7 +180,8 @@ def upload_file(file_path, folder_id=None, service=None, auth_method='service_ac 'file_name': file.get('name'), 'size': remote_size, 'local_hash': local_hash, - 'verified': True + 'verified': True, + 'ownership_transferred': ownership_transferred } else: print(f"[DriveSend] ⚠ Upload size mismatch: local={local_size}, remote={remote_size}") @@ -128,7 +192,8 @@ def upload_file(file_path, folder_id=None, service=None, auth_method='service_ac 'size': remote_size, 'local_hash': local_hash, 'verified': False, - 'warning': 'Size mismatch - file may be corrupted' + 'warning': 'Size mismatch - file may be corrupted', + 'ownership_transferred': ownership_transferred } except HttpError as e: @@ -173,7 +238,8 @@ def create_folder(folder_name, parent_folder_id=None, service=None, auth_method= try: folder = service.files().create( body=file_metadata, - fields='id, name' + fields='id, name', + supportsAllDrives=True ).execute() print(f"[DriveSend] Created folder: {folder_name} (ID: {folder.get('id')})") @@ -217,7 +283,9 @@ def find_folder(folder_name, parent_folder_id=None, service=None, auth_method='s results = service.files().list( q=query, fields='files(id, name)', - pageSize=1 + pageSize=1, + supportsAllDrives=True, + includeItemsFromAllDrives=True ).execute() files = results.get('files', [])