diff --git a/.github/workflows/auto-tag.yaml b/.github/workflows/auto-tag.yaml deleted file mode 100644 index 19eafd6..0000000 --- a/.github/workflows/auto-tag.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: Auto Tag Release - -on: - push: - branches: - - 'release/*' - -jobs: - create-tag: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Extract tag name from branch - id: extract_tag - run: | - BRANCH_NAME="${GITHUB_REF#refs/heads/}" - TAG_NAME="${BRANCH_NAME#release/}" - echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT - echo "Extracted tag name: $TAG_NAME" - - - name: Check if tag already exists - id: check_tag - run: | - if git rev-parse --verify "refs/tags/${{ steps.extract_tag.outputs.tag_name }}" >/dev/null 2>&1; then - echo "tag_exists=true" >> $GITHUB_OUTPUT - echo "Tag ${{ steps.extract_tag.outputs.tag_name }} already exists" - else - echo "tag_exists=false" >> $GITHUB_OUTPUT - echo "Tag ${{ steps.extract_tag.outputs.tag_name }} does not exist" - fi - - - name: Create and push tag - if: steps.check_tag.outputs.tag_exists == 'false' - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git tag -a "${{ steps.extract_tag.outputs.tag_name }}" -m "Release ${{ steps.extract_tag.outputs.tag_name }}" - git push origin "${{ steps.extract_tag.outputs.tag_name }}" - - - name: Skip tag creation - if: steps.check_tag.outputs.tag_exists == 'true' - run: | - echo "Tag ${{ steps.extract_tag.outputs.tag_name }} already exists, skipping creation" \ No newline at end of file diff --git a/.github/workflows/publish-plugin.yaml b/.github/workflows/publish-plugin.yaml deleted file mode 100644 index 5704c78..0000000 --- a/.github/workflows/publish-plugin.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Release Obsidian plugin - -on: - push: - tags: - - "*" - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - - name: Setup Bun - uses: oven-sh/setup-bun@v1 - with: - bun-version: latest - - - name: Build plugin - run: | - cd packages/plugin - bun install - bun run build - - - name: Create release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - tag="${GITHUB_REF#refs/tags/}" - - cd packages/plugin - gh release create "$tag" \ - --title="$tag" \ - --draft \ - main.js manifest.json styles.css diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..140c20b --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,85 @@ +name: Release Plugin + +on: + push: + branches: + - 'release/*' + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from branch + id: extract_version + run: | + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + VERSION="${BRANCH_NAME#release/}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Check if tag already exists + id: check_tag + run: | + if git rev-parse --verify "refs/tags/${{ steps.extract_version.outputs.version }}" >/dev/null 2>&1; then + echo "tag_exists=true" >> $GITHUB_OUTPUT + echo "Tag ${{ steps.extract_version.outputs.version }} already exists" + else + echo "tag_exists=false" >> $GITHUB_OUTPUT + echo "Tag ${{ steps.extract_version.outputs.version }} does not exist" + fi + + - name: Setup Bun + if: steps.check_tag.outputs.tag_exists == 'false' + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + if: steps.check_tag.outputs.tag_exists == 'false' + run: bun install + + - name: Run tests + if: steps.check_tag.outputs.tag_exists == 'false' + run: cd packages/plugin && bun test + + - name: Build plugin + if: steps.check_tag.outputs.tag_exists == 'false' + run: | + cd packages/plugin + bun run build + + - name: Create and push tag + if: steps.check_tag.outputs.tag_exists == 'false' + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git tag -a "${{ steps.extract_version.outputs.version }}" -m "Release ${{ steps.extract_version.outputs.version }}" + git push origin "${{ steps.extract_version.outputs.version }}" + + - name: Create GitHub release + if: steps.check_tag.outputs.tag_exists == 'false' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.extract_version.outputs.version }}" + cd packages/plugin + + # Create release with plugin files + gh release create "$VERSION" \ + --title="$VERSION" \ + --notes="Release $VERSION - See CHANGELOG.md for details" \ + --draft \ + main.js manifest.json styles.css + + - name: Skip release (tag exists) + if: steps.check_tag.outputs.tag_exists == 'true' + run: | + echo "Tag ${{ steps.extract_version.outputs.version }} already exists" + echo "If you want to re-release, delete the tag first: git push --delete origin ${{ steps.extract_version.outputs.version }}" diff --git a/README.md b/README.md index decda9d..eda0e16 100644 --- a/README.md +++ b/README.md @@ -2,245 +2,286 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -A complete synchronization solution for Obsidian vaults with Google Drive backend, featuring real-time sync, folder structure preservation, and automatic conflict detection. +

+ Vync - Obsidian to Google Drive Sync +

+ +A local-first Obsidian plugin for seamless vault synchronization with Google Drive. Direct integration, no server required, complete privacy and control. ## โœจ Features - ๐Ÿ”„ **Bi-directional Sync** - Upload and download files between Obsidian and Google Drive - ๐Ÿ“ **Folder Preservation** - Maintains complete directory hierarchy in Google Drive - โšก **Real-time Updates** - Files sync immediately on creation or modification -- ๐Ÿ” **OAuth 2.0 Auth** - Secure Google Drive authentication -- ๐ŸŒ **Remote Deployment** - Works on any hosting platform with auto-detected URLs -- ๐Ÿ” **Index Reconciliation** - Periodic scanning ensures all files are tracked -- ๐Ÿ’พ **Local Storage** - Development mode with local file storage -- ๐Ÿš€ **Auto-Detection** - Dynamic configuration for any environment +- ๐Ÿ” **OAuth 2.0 Auth** - Secure Google Drive authentication directly from plugin +- ๐Ÿ’ป **Local-First** - No external server required, runs entirely in Obsidian +- ๐Ÿ” **Smart Sync** - Intelligent change detection and conflict resolution +- ๐Ÿ—‘๏ธ **Tombstone Management** - Proper deletion tracking with grace periods +- ๐Ÿ”„ **File Rename Detection** - Handles file and folder renames efficiently ## ๐Ÿš€ Quick Start -### 1. Clone and Install +### 1. Install Plugin + +#### Option A: From Obsidian Community Plugins +1. Open Obsidian Settings โ†’ Community Plugins +2. Search for "Vync" +3. Click Install +4. Enable the plugin + +#### Option B: Manual Installation ```bash +# Clone and build git clone cd vync bun install -``` - -### 2. Setup Server -```bash -cd packages/server -cp .env.example .env -# Edit .env with your configuration -bun run dev -``` -### 3. Setup Obsidian Plugin -```bash +# Build the plugin cd packages/plugin bun run build -# Install plugin in Obsidian + +# Copy to your vault's plugins folder +cp -r dist/* /path/to/your/vault/.obsidian/plugins/vync/ ``` +### 2. Configure Google Drive +1. Open Vync settings in Obsidian +2. Click "Authenticate with Google Drive" +3. Complete OAuth flow +4. Start syncing! + **๐Ÿ“– Detailed guide:** See [QUICK_START.md](QUICK_START.md) ## ๐Ÿ“‹ Documentation ### Getting Started -- **[Quick Start Guide](QUICK_START.md)** - Get running in 10 minutes -- **[Environment Setup](packages/server/ENV_SETUP_GUIDE.md)** - Configure environment variables -- **[Deployment Guide](DEPLOYMENT_GUIDE.md)** - Deploy to production +- **[Quick Start Guide](QUICK_START.md)** - Get running in 5 minutes +- **[Google OAuth Setup](docs/OAUTH_SETUP.md)** - Configure Google Drive API ### Features +- **[Sync Logic](docs/SYNC_LOGIC.md)** - How the sync algorithm works - **[Folder Sync](FOLDER_SYNC_DOCUMENTATION.md)** - Folder structure preservation -- **[OAuth Setup](OAUTH_REDIRECT_FIX.md)** - Dynamic OAuth configuration -- **[Binary Files](PDF_UPLOAD_FIX.md)** - PDF and image upload support +- **[Conflict Resolution](docs/CONFLICTS.md)** - Handling sync conflicts +- **[Tombstone Management](docs/TOMBSTONES.md)** - Deletion tracking ### Reference -- **[Environment Variables](ENVIRONMENT_VARIABLES.md)** - All configuration options -- **[Server API](packages/server/README.md)** - Server documentation +- **[Plugin API](packages/plugin/README.md)** - Plugin documentation - **[Changelog](CHANGELOG.md)** - Version history -- **[Summary](SUMMARY.md)** - Project overview +- **[Architecture](docs/ARCHITECTURE.md)** - Technical overview ## ๐Ÿ—๏ธ Architecture +Vync is a **local-first** plugin that runs entirely within Obsidian: + ``` -vync/ -โ”œโ”€โ”€ packages/ -โ”‚ โ”œโ”€โ”€ server/ # Backend sync server -โ”‚ โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ routes/ # API endpoints -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ services/# Drive services -โ”‚ โ”‚ โ””โ”€โ”€ .env # Configuration -โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€ plugin/ # Obsidian plugin -โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”œโ”€โ”€ services/# Sync services -โ”‚ โ”‚ โ””โ”€โ”€ main.ts # Plugin entry -โ”‚ โ””โ”€โ”€ manifest.json -โ”‚ -โ””โ”€โ”€ docs/ # Documentation +vync/packages/plugin/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ main.ts # Plugin entry point +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”‚ โ”œโ”€โ”€ googleDriveAuth.ts # OAuth authentication +โ”‚ โ”‚ โ”œโ”€โ”€ googleDriveService.ts# Google Drive API client +โ”‚ โ”‚ โ”œโ”€โ”€ syncService.ts # Sync orchestration +โ”‚ โ”‚ โ”œโ”€โ”€ syncState.ts # State management +โ”‚ โ”‚ โ”œโ”€โ”€ tombstoneManager.ts # Deletion tracking +โ”‚ โ”‚ โ”œโ”€โ”€ vaultWatcher.ts # File change detection +โ”‚ โ”‚ โ””โ”€โ”€ conflictUI.ts # Conflict resolution UI +โ”‚ โ””โ”€โ”€ types.ts # TypeScript definitions +โ”œโ”€โ”€ tests/ # Unit tests +โ””โ”€โ”€ manifest.json # Plugin manifest ``` +### How It Works + +1. **Direct Integration** - Plugin communicates directly with Google Drive API +2. **Local State** - Sync state stored in `.obsidian/plugins/vync/` +3. **OAuth Tokens** - Encrypted and stored locally +4. **No Server** - Zero external dependencies, completely private + ## ๐Ÿ”ง Configuration -### Server (.env) -```bash -# Storage backend -DRIVE_TYPE=google # 'local' or 'google' +### Google Drive API Setup -# Google OAuth (if using Google Drive) -GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com -GOOGLE_CLIENT_SECRET=xxx +1. **Create Google Cloud Project** + - Go to [Google Cloud Console](https://console.cloud.google.com/) + - Create a new project + - Enable Google Drive API -# Optional - auto-detected if not set -GOOGLE_REDIRECT_URI=https://your-domain.com/auth/google/callback -``` +2. **Configure OAuth Credentials** + - Create OAuth 2.0 Client ID (Desktop app type) + - Download credentials JSON + - Add to plugin settings + +### Plugin Settings (in Obsidian) -### Obsidian Plugin ``` Settings โ†’ Vync: -- Server URL: http://localhost:3000 -- Vault ID: my-vault -- Auto Sync: ON +โ”œโ”€โ”€ Google OAuth +โ”‚ โ”œโ”€โ”€ Client ID: [from Google Cloud] +โ”‚ โ”œโ”€โ”€ Client Secret: [from Google Cloud] +โ”‚ โ””โ”€โ”€ Status: โœ“ Connected +โ”œโ”€โ”€ Sync Settings +โ”‚ โ”œโ”€โ”€ Auto Sync: ON +โ”‚ โ”œโ”€โ”€ Sync Interval: 5 minutes +โ”‚ โ””โ”€โ”€ Conflict Resolution: Prompt +โ””โ”€โ”€ Advanced + โ”œโ”€โ”€ Tombstone Grace Period: 30 days + โ””โ”€โ”€ Debug Logging: OFF ``` -## ๐ŸŒ Deployment +## ๐Ÿ“ฆ Distribution -### Supported Platforms -- โœ… Vercel (Serverless) -- โœ… Railway (Container) -- โœ… Render (Web Service) -- โœ… Docker (Container) -- โœ… VPS (PM2) +### Publishing to Community Plugins -### Quick Deploy -```bash -# Vercel -vercel deploy +When ready to publish: -# Railway -railway up +1. **Create Release Branch** + ```bash + git checkout -b release/v1.0.0 + git push origin release/v1.0.0 + ``` -# Docker -docker build -t vync . -docker run -p 3000:3000 vync -``` +2. **Automatic Release** - GitHub Actions will: + - Run tests + - Build plugin + - Create version tag + - Create GitHub release + - Attach `main.js`, `manifest.json`, `styles.css` -**๐Ÿ“– Full guide:** [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) +3. **Submit to Obsidian** + - Fork [obsidian-releases](https://github.com/obsidianmd/obsidian-releases) + - Add your plugin to `community-plugins.json` + - Submit PR ## ๐Ÿ“Š How It Works ### Sync Flow -1. **File Created** in Obsidian -2. **Plugin Detects** change via VaultWatcher -3. **Immediate Upload** to server -4. **Server Uploads** to Google Drive -5. **Index Updated** with file metadata -6. **Folder Structure** created automatically - -### Index Reconciliation -- Runs every 5 minutes automatically -- Detects files created outside Obsidian -- Ensures vault and index parity -- Manual trigger via command palette - -### Conflict Resolution -- Automatic detection of conflicts -- User-prompted resolution -- Preserves both versions -- Tracks conflict history - -## ๐Ÿ” Security - -### Best Practices -- โœ… HTTPS in production -- โœ… Environment-specific credentials -- โœ… Restricted CORS origins -- โœ… Sensitive files in .gitignore -- โœ… OAuth token encryption - -### Protected Files +1. **File Changed** in Obsidian +2. **VaultWatcher Detects** change event +3. **SyncService** determines action (upload/download/conflict) +4. **Direct Upload/Download** to/from Google Drive +5. **State Updated** locally with revision IDs +6. **Folder Structure** mirrored automatically + +### Smart Sync Features + +**Three-Way Comparison** +- Compares: Local file โ†” Last sync state โ†” Remote file +- Uses revision IDs (not timestamps) for accuracy +- Detects: unchanged, local-only, remote-only, conflicts + +**Tombstone Management** +- Tracks deletions with grace periods (default 30 days) +- Syncs deletions across devices +- Prevents accidental data loss + +**Conflict Resolution** +- Detects simultaneous changes on multiple devices +- Presents user with resolution options +- Preserves both versions if requested + +## ๐Ÿ” Security & Privacy + +### Local-First Design +- โœ… **No External Server** - Direct vault-to-Google Drive sync +- โœ… **Local OAuth Tokens** - Encrypted and stored in your vault +- โœ… **No Data Collection** - Zero telemetry or analytics +- โœ… **Open Source** - Fully auditable code +- โœ… **Standard APIs** - Uses official Google Drive API only + +### Data Storage ``` -.env -.env.production -oauth-tokens.json -credentials.json +.obsidian/plugins/vync/ +โ”œโ”€โ”€ data.json # Plugin settings (no secrets) +โ”œโ”€โ”€ sync-state.json # Sync index (file hashes, IDs) +โ”œโ”€โ”€ tombstones.json # Deletion tracking +โ””โ”€โ”€ .oauth-tokens.json # Encrypted OAuth tokens (local only) ``` +**Note:** OAuth tokens never leave your device and are encrypted at rest. + ## ๐Ÿงช Testing -### Manual Tests +### Development ```bash -# Test file sync -1. Create file in Obsidian -2. Check console for upload -3. Verify in Google Drive - -# Test folder sync -1. Create nested folders -2. Add files to folders -3. Check Drive structure - -# Test OAuth -1. Visit /auth/google -2. Complete flow -3. Check /auth/status -``` +cd packages/plugin -### Commands -```bash -# Server health -curl http://localhost:3000/health +# Run unit tests +bun test + +# Run linter +bun run lint -# Auth status -curl http://localhost:3000/auth/status +# Build plugin +bun run build + +# Watch mode +bun run dev +``` -# Plugin commands (in Obsidian) -- "Sync Vault" -- "Reconcile Sync Index" -- "Test Connection" +### Plugin Commands (in Obsidian) ``` +Cmd/Ctrl + P โ†’ Search for: +โ”œโ”€โ”€ "Vync: Full Sync" - Sync entire vault +โ”œโ”€โ”€ "Vync: Sync Status" - View sync state +โ”œโ”€โ”€ "Vync: Resolve Conflicts" - Handle conflicts +โ””โ”€โ”€ "Vync: Clear Cache" - Reset sync state +``` + +### Manual Testing +1. **File Sync** - Create/edit files, verify in Drive +2. **Folder Sync** - Create nested folders, check structure +3. **Renames** - Rename files/folders, verify tracking +4. **Deletions** - Delete files, check tombstones +5. **Conflicts** - Edit same file on two devices simultaneously ## ๐Ÿ› Troubleshooting ### Common Issues -**Server won't start** -```bash -# Check port availability -lsof -i :3000 -# Use different port -PORT=8080 bun run dev -``` +**OAuth Authentication Failed** +- Verify Client ID and Secret are correct +- Check OAuth consent screen is configured +- Ensure "Desktop app" type is selected +- Try removing and re-adding credentials -**OAuth errors** -```bash -# Add redirect URI to Google Console -http://localhost:3000/auth/google/callback -``` +**Files Not Syncing** +- Check sync status in plugin settings +- Verify Google Drive has sufficient space +- Look for conflicts in conflict resolution UI +- Check Obsidian Developer Console (Cmd/Ctrl + Shift + I) for errors -**Files not syncing** -```bash -# Run index reconciliation -Cmd/Ctrl + P โ†’ "Reconcile Sync Index" -``` +**Sync Conflicts** +- Use "Vync: Resolve Conflicts" command +- Choose which version to keep +- Or keep both versions with different names + +**Plugin Not Loading** +- Check `.obsidian/plugins/vync/` exists +- Verify `manifest.json`, `main.js`, `styles.css` are present +- Enable plugin in Settings โ†’ Community Plugins +- Check Obsidian console for error messages -**๐Ÿ“– More solutions:** See documentation links above +**๐Ÿ“– More help:** [Open an issue](https://github.com/your-repo/issues) ## ๐Ÿ“ˆ Roadmap -### v1.1 (Short Term) -- [ ] File deletion from Google Drive -- [ ] Chunked large file uploads -- [ ] Selective sync patterns +### v1.1 (Next Release) +- [ ] Delta sync for large files +- [ ] Selective sync (exclude patterns) +- [ ] Bandwidth optimization +- [ ] Sync statistics dashboard -### v1.2 (Medium Term) -- [ ] Real-time collaboration -- [ ] Mobile app support -- [ ] Alternative storage backends +### v1.2 (Future) +- [ ] Mobile app support (iOS/Android) +- [ ] Alternative backends (Dropbox, OneDrive) +- [ ] End-to-end encryption option +- [ ] Shared vault collaboration ### v2.0 (Long Term) -- [ ] End-to-end encryption -- [ ] Multi-user support -- [ ] Advanced conflict resolution +- [ ] Multi-device conflict resolution UI +- [ ] Advanced sync rules engine +- [ ] Plugin marketplace integration +- [ ] Automated backups and versioning ## ๐Ÿค Contributing @@ -266,9 +307,10 @@ Built with: ## ๐Ÿ’ฌ Support -- **Documentation**: See links above -- **Issues**: GitHub Issues -- **Discussions**: GitHub Discussions +- **Documentation**: See [Documentation](#-documentation) section +- **Bug Reports**: [GitHub Issues](https://github.com/your-repo/issues) +- **Feature Requests**: [GitHub Discussions](https://github.com/your-repo/discussions) +- **Questions**: [GitHub Discussions Q&A](https://github.com/your-repo/discussions/categories/q-a) ## โญ Star History @@ -276,8 +318,6 @@ If this project helps you, consider giving it a star! --- -**Status**: Production Ready โœ… -**Version**: 1.0.0 -**Last Updated**: 2024 +**Status**: Beta Testing ๐Ÿšง -[Quick Start](QUICK_START.md) | [Documentation](SUMMARY.md) | [Deployment](DEPLOYMENT_GUIDE.md) | [Contributing](#contributing) \ No newline at end of file +[Quick Start](QUICK_START.md) | [Documentation](#-documentation) | [Architecture](#-architecture) | [Contributing](#-contributing) \ No newline at end of file diff --git a/assets/illustration.png b/assets/illustration.png new file mode 100644 index 0000000..73f2dda Binary files /dev/null and b/assets/illustration.png differ