# IGNY8 Plugin Distribution System **Created:** January 9, 2026 **Version:** 1.0 **Status:** ✅ Phase 1 Implemented **Scope:** WordPress, Shopify, Custom Site Integration Plugins --- ## Overview This document outlines the architecture for distributing and auto-updating IGNY8 integration plugins across multiple platforms (WordPress, Shopify, Custom Sites). --- ## 1. Directory Structure ### 1.1 - Server Location ``` /data/app/igny8/ ├── backend/ │ └── igny8_core/ │ └── plugins/ # NEW: Plugin management │ ├── __init__.py │ ├── models.py # Plugin version tracking │ ├── views.py # Download & update API │ ├── serializers.py │ ├── urls.py │ └── utils.py # ZIP compression, versioning ├── plugins/ # NEW: Plugin source & distributions │ ├── wordpress/ │ │ ├── source/ # Development source files │ │ │ └── igny8-wp-bridge/ │ │ │ ├── igny8-wp-bridge.php │ │ │ ├── includes/ │ │ │ ├── assets/ │ │ │ └── readme.txt │ │ └── dist/ # Compiled/zipped releases │ │ ├── igny8-wp-bridge-v1.0.0.zip │ │ ├── igny8-wp-bridge-v1.0.1.zip │ │ └── igny8-wp-bridge-latest.zip # Symlink to latest │ ├── shopify/ # FUTURE │ │ ├── source/ │ │ └── dist/ │ └── custom-site/ # FUTURE │ ├── source/ │ └── dist/ └── docs/ └── plans/ └── PLUGIN-DISTRIBUTION-SYSTEM.md ``` ### 1.2 - Why This Structure? | Location | Purpose | |----------|---------| | `/plugins/` | Separate from app code, easy to manage | | `/plugins/{platform}/source/` | Development files, version controlled | | `/plugins/{platform}/dist/` | Ready-to-download ZIP files | | `/backend/igny8_core/plugins/` | Django app for API & version management | --- ## 2. Database Models ### 2.1 - Plugin Model ```python # backend/igny8_core/plugins/models.py class Plugin(models.Model): """Represents a plugin type (WordPress, Shopify, etc.)""" PLATFORM_CHOICES = [ ('wordpress', 'WordPress'), ('shopify', 'Shopify'), ('custom', 'Custom Site'), ] name = models.CharField(max_length=100) # "IGNY8 WP Bridge" slug = models.SlugField(unique=True) # "igny8-wp-bridge" platform = models.CharField(max_length=20, choices=PLATFORM_CHOICES) description = models.TextField() is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'plugins' class PluginVersion(models.Model): """Tracks each version of a plugin""" STATUS_CHOICES = [ ('draft', 'Draft'), # In development ('testing', 'Testing'), # Internal testing ('staged', 'Staged'), # Ready, not yet pushed ('released', 'Released'), # Available for download ('update_ready', 'Update Ready'), # Push to installed sites ('deprecated', 'Deprecated'), # Old version ] plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE, related_name='versions') version = models.CharField(max_length=20) # "1.0.0", "1.0.1" version_code = models.IntegerField() # 100, 101 (for comparison) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft') # File info file_path = models.CharField(max_length=500) # Relative path in dist/ file_size = models.IntegerField(default=0) # Bytes checksum = models.CharField(max_length=64) # SHA256 for integrity # Release info changelog = models.TextField(blank=True) min_api_version = models.CharField(max_length=20, default='1.0') # Timestamps created_at = models.DateTimeField(auto_now_add=True) released_at = models.DateTimeField(null=True, blank=True) # Auto-update control force_update = models.BooleanField(default=False) # Critical security fix class Meta: db_table = 'plugin_versions' unique_together = ['plugin', 'version'] ordering = ['-version_code'] class PluginInstallation(models.Model): """Tracks where plugins are installed (per site)""" site = models.ForeignKey('Site', on_delete=models.CASCADE, related_name='plugin_installations') plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE) current_version = models.ForeignKey(PluginVersion, on_delete=models.SET_NULL, null=True) # Installation status is_active = models.BooleanField(default=True) last_health_check = models.DateTimeField(null=True) # Update tracking pending_update = models.ForeignKey( PluginVersion, on_delete=models.SET_NULL, null=True, related_name='pending_installations' ) update_notified_at = models.DateTimeField(null=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'plugin_installations' unique_together = ['site', 'plugin'] ``` --- ## 3. API Endpoints ### 3.1 - Public Endpoints (No Auth Required) ```python # Download latest plugin (for initial installation) GET /api/plugins/{slug}/download/ # Returns: ZIP file download # Check for updates (called by installed plugins) GET /api/plugins/{slug}/check-update/?current_version=1.0.0 # Returns: { "update_available": true, "latest_version": "1.0.1", "download_url": "...", "changelog": "..." } ``` ### 3.2 - Authenticated Endpoints ```python # Get download URL for user (tracks downloads) GET /api/plugins/{slug}/download-url/ # Returns: { "url": "...", "expires_at": "..." } # Report installation (called after WP plugin is activated) POST /api/plugins/{slug}/register-installation/ # Body: { "site_id": 123, "version": "1.0.0" } # Update installation status (health check) POST /api/plugins/{slug}/health-check/ # Body: { "site_id": 123, "version": "1.0.0", "status": "active" } ``` ### 3.3 - Admin Endpoints ```python # List all plugin versions GET /api/admin/plugins/ # Create new version POST /api/admin/plugins/{slug}/versions/ # Body: { "version": "1.0.1", "changelog": "...", "status": "draft" } # Upload plugin ZIP POST /api/admin/plugins/{slug}/versions/{version}/upload/ # Body: multipart/form-data with ZIP file # Release version (make available for download) POST /api/admin/plugins/{slug}/versions/{version}/release/ # Push update to installed sites POST /api/admin/plugins/{slug}/versions/{version}/push-update/ # This sets status to 'update_ready' and notifies all installations ``` --- ## 4. WordPress Plugin Update Mechanism ### 4.1 - How WordPress Auto-Updates Work WordPress checks for plugin updates by calling a filter. Our plugin hooks into this: ```php // In igny8-wp-bridge.php class IGNY8_WP_Bridge { private $plugin_slug = 'igny8-wp-bridge'; private $version = '1.0.0'; private $api_url = 'https://app.igny8.com/api/plugins/'; public function __construct() { // Check for updates add_filter('pre_set_site_transient_update_plugins', [$this, 'check_for_updates']); add_filter('plugins_api', [$this, 'plugin_info'], 10, 3); } public function check_for_updates($transient) { if (empty($transient->checked)) { return $transient; } // Call IGNY8 API to check for updates $response = wp_remote_get($this->api_url . $this->plugin_slug . '/check-update/', [ 'timeout' => 10, 'headers' => [ 'X-IGNY8-Site-ID' => get_option('igny8_site_id'), 'X-IGNY8-API-Key' => get_option('igny8_api_key'), ], 'body' => [ 'current_version' => $this->version, ] ]); if (is_wp_error($response)) { return $transient; } $data = json_decode(wp_remote_retrieve_body($response), true); if (!empty($data['update_available'])) { $plugin_file = $this->plugin_slug . '/' . $this->plugin_slug . '.php'; $transient->response[$plugin_file] = (object) [ 'slug' => $this->plugin_slug, 'new_version' => $data['latest_version'], 'package' => $data['download_url'], 'url' => $data['info_url'] ?? '', ]; } return $transient; } public function plugin_info($result, $action, $args) { if ($action !== 'plugin_information' || $args->slug !== $this->plugin_slug) { return $result; } // Fetch plugin info from IGNY8 API $response = wp_remote_get($this->api_url . $this->plugin_slug . '/info/'); if (is_wp_error($response)) { return $result; } $data = json_decode(wp_remote_retrieve_body($response), true); return (object) [ 'name' => $data['name'], 'slug' => $this->plugin_slug, 'version' => $data['version'], 'author' => 'IGNY8', 'homepage' => 'https://igny8.com', 'sections' => [ 'description' => $data['description'], 'changelog' => $data['changelog'], ], 'download_link' => $data['download_url'], ]; } } ``` ### 4.2 - Update Flow ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Plugin Update Flow │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 1. Admin marks version as "Update Ready" in IGNY8 Dashboard │ │ │ │ │ ▼ │ │ 2. IGNY8 API returns update_available=true for check-update calls │ │ │ │ │ ▼ │ │ 3. WordPress cron checks for updates (or user clicks "Check Now") │ │ │ │ │ ▼ │ │ 4. WP Plugin calls IGNY8 API → Gets new version info │ │ │ │ │ ▼ │ │ 5. WordPress shows "Update Available" in Plugins page │ │ │ │ │ ▼ │ │ 6. User clicks Update (or auto-update if enabled) │ │ │ │ │ ▼ │ │ 7. WordPress downloads ZIP from IGNY8 and installs │ │ │ │ │ ▼ │ │ 8. Plugin activation hook reports new version to IGNY8 │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 5. Initial Download (ZIP Generation) ### 5.1 - Build Script ```bash #!/bin/bash # scripts/build-wp-plugin.sh VERSION=$1 PLUGIN_NAME="igny8-wp-bridge" SOURCE_DIR="/data/app/igny8/plugins/wordpress/source/${PLUGIN_NAME}" DIST_DIR="/data/app/igny8/plugins/wordpress/dist" if [ -z "$VERSION" ]; then echo "Usage: ./build-wp-plugin.sh " exit 1 fi # Create dist directory if not exists mkdir -p "$DIST_DIR" # Create temp directory for packaging TEMP_DIR=$(mktemp -d) PACKAGE_DIR="${TEMP_DIR}/${PLUGIN_NAME}" # Copy source files cp -r "$SOURCE_DIR" "$PACKAGE_DIR" # Update version in main plugin file sed -i "s/Version: .*/Version: ${VERSION}/" "${PACKAGE_DIR}/${PLUGIN_NAME}.php" sed -i "s/\$version = '.*'/\$version = '${VERSION}'/" "${PACKAGE_DIR}/${PLUGIN_NAME}.php" # Create ZIP cd "$TEMP_DIR" zip -r "${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.zip" "$PLUGIN_NAME" # Update latest symlink ln -sf "${PLUGIN_NAME}-v${VERSION}.zip" "${DIST_DIR}/${PLUGIN_NAME}-latest.zip" # Calculate checksum sha256sum "${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.zip" > "${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.sha256" # Cleanup rm -rf "$TEMP_DIR" echo "Built: ${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.zip" echo "Checksum: $(cat ${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.sha256)" ``` ### 5.2 - Django Management Command ```python # backend/igny8_core/plugins/management/commands/build_plugin.py from django.core.management.base import BaseCommand import subprocess import hashlib from plugins.models import Plugin, PluginVersion class Command(BaseCommand): help = 'Build and register a new plugin version' def add_arguments(self, parser): parser.add_argument('--plugin', type=str, required=True) parser.add_argument('--version', type=str, required=True) parser.add_argument('--changelog', type=str, default='') parser.add_argument('--release', action='store_true') def handle(self, *args, **options): plugin = Plugin.objects.get(slug=options['plugin']) version = options['version'] # Run build script result = subprocess.run( ['./scripts/build-wp-plugin.sh', version], capture_output=True, text=True ) if result.returncode != 0: self.stderr.write(f"Build failed: {result.stderr}") return # Calculate version code (1.0.1 -> 101) parts = version.split('.') version_code = int(parts[0]) * 10000 + int(parts[1]) * 100 + int(parts[2]) # Get file info file_path = f"{plugin.slug}-v{version}.zip" full_path = f"/data/app/igny8/plugins/wordpress/dist/{file_path}" with open(full_path, 'rb') as f: checksum = hashlib.sha256(f.read()).hexdigest() import os file_size = os.path.getsize(full_path) # Create version record plugin_version, created = PluginVersion.objects.update_or_create( plugin=plugin, version=version, defaults={ 'version_code': version_code, 'file_path': file_path, 'file_size': file_size, 'checksum': checksum, 'changelog': options['changelog'], 'status': 'released' if options['release'] else 'draft', } ) action = 'Created' if created else 'Updated' self.stdout.write(f"{action} version {version} for {plugin.name}") ``` --- ## 6. Frontend Integration ### 6.1 - Download Button Component ```tsx // frontend/src/components/sites/PluginDownloadSection.tsx import { Button } from '@/components/ui'; import { useQuery } from '@tanstack/react-query'; import { api } from '@/services/api'; interface PluginInfo { name: string; slug: string; version: string; download_url: string; file_size: number; platform: string; } export function PluginDownloadSection({ platform = 'wordpress' }: { platform: string }) { const { data: plugin, isLoading } = useQuery({ queryKey: ['plugin', platform], queryFn: () => api.get(`/plugins/${platform}/latest/`).then(r => r.data), }); const handleDownload = async () => { if (!plugin) return; // Track download await api.post(`/plugins/${plugin.slug}/track-download/`); // Trigger download window.location.href = plugin.download_url; }; if (isLoading) return
Loading...
; if (!plugin) return null; return (

{plugin.name}

Download and install the plugin on your {platform === 'wordpress' ? 'WordPress' : platform} site

Version {plugin.version} {formatFileSize(plugin.file_size)}
); } ``` ### 6.2 - Admin Plugin Management Page (Future) ``` /admin/plugins # List all plugins /admin/plugins/{slug} # Plugin details + versions /admin/plugins/{slug}/versions # Version history /admin/plugins/{slug}/upload # Upload new version ``` --- ## 7. Security Considerations ### 7.1 - Download Security | Measure | Implementation | |---------|----------------| | Signed URLs | Time-limited download URLs (15 min expiry) | | Checksum | SHA256 verification in WP plugin | | API Key Required | Downloads require valid site API key | | Rate Limiting | Max 10 downloads/hour per site | ### 7.2 - Update Security | Measure | Implementation | |---------|----------------| | HTTPS Only | All API calls over TLS | | Version Signing | Checksum comparison before install | | Rollback | Keep last 3 versions for emergency rollback | | Staged Rollout | Option to release to % of installs first | --- ## 8. Implementation Phases ### Phase 1: Basic Infrastructure (Week 1) ✅ COMPLETED - [x] Create `/plugins/` directory structure - [x] Create Django `plugins` app with models - [x] Migrate existing WP plugin to `/plugins/wordpress/source/` - [x] Create build script for ZIP generation - [x] Implement download API endpoint ### Phase 2: Frontend Integration (Week 1) ✅ COMPLETED - [x] Update Site Settings > Integrations download button - [x] Create plugin info API endpoint - [x] Add version display to download section ### Phase 3: Update System (Week 2) ✅ COMPLETED - [x] Implement check-update API - [x] Add update hooks to WP plugin - [x] Create PluginInstallation tracking - [x] Build admin version management UI ### Phase 4: Advanced Features (Week 3) - [ ] Signed download URLs - [ ] Version rollback support - [ ] Update notifications in IGNY8 dashboard - [ ] Staged rollout percentage ### Phase 5: Additional Platforms (Future) - [ ] Shopify plugin structure - [ ] Custom site SDK/package - [ ] Platform-specific update mechanisms --- ## 9. File Serving Configuration ### 9.1 - Nginx Configuration ```nginx # Add to nginx config for igny8 # Serve plugin downloads location /plugins/download/ { alias /data/app/igny8/plugins/; # Only allow ZIP files location ~ \.zip$ { add_header Content-Disposition 'attachment'; add_header X-Content-Type-Options 'nosniff'; } # Deny access to source files location ~ /source/ { deny all; } } ``` ### 9.2 - Django URL (Proxied Downloads) ```python # Better approach: Django handles download with auth # backend/igny8_core/plugins/views.py from django.http import FileResponse from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny @api_view(['GET']) @permission_classes([AllowAny]) def download_plugin(request, slug): plugin = get_object_or_404(Plugin, slug=slug, is_active=True) latest = plugin.versions.filter(status__in=['released', 'update_ready']).first() if not latest: return Response({'error': 'No version available'}, status=404) file_path = f'/data/app/igny8/plugins/{plugin.platform}/dist/{latest.file_path}' response = FileResponse( open(file_path, 'rb'), content_type='application/zip' ) response['Content-Disposition'] = f'attachment; filename="{latest.file_path}"' return response ``` --- ## 10. Monitoring & Analytics ### 10.1 - Metrics to Track | Metric | Purpose | |--------|---------| | Downloads per version | Adoption rate | | Active installations | Current reach | | Update success rate | Quality indicator | | Version distribution | Upgrade urgency | | Health check failures | Support needs | ### 10.2 - Admin Dashboard Widgets - **Plugin Downloads** - Daily/weekly download counts - **Version Distribution** - Pie chart of installed versions - **Update Adoption** - Time to 80% adoption after release - **Installation Health** - Sites with outdated/failing plugins --- ## Quick Reference ### Commands ```bash # Build new version python manage.py build_plugin --plugin=igny8-wp-bridge --version=1.0.1 --changelog="Bug fixes" # Release version (make downloadable) python manage.py release_plugin --plugin=igny8-wp-bridge --version=1.0.1 # Push update to sites python manage.py push_plugin_update --plugin=igny8-wp-bridge --version=1.0.1 ``` ### API Quick Reference ``` GET /api/plugins/{slug}/download/ # Download latest ZIP GET /api/plugins/{slug}/check-update/ # Check for updates GET /api/plugins/{slug}/info/ # Plugin metadata POST /api/plugins/{slug}/register/ # Register installation POST /api/plugins/{slug}/health-check/ # Report status ``` --- **Document Owner:** IGNY8 Team **Next Review:** After Phase 1 implementation