Skip to content

RFC: Support Release-Based & Archive (ZIP) Plugin Updates #64

Description

@adrighem

RFC: Release-Based & Archive (ZIP) Plugin Updates

1. Executive Summary

Currently, PyPluginStore relies exclusively on git cloning and git pull commands to install and update Python plugins. This requires every Domoticz host to have git installed, uses significant bandwidth/disk space for repository history, and pulls raw master branches which may contain bleeding-edge, unstable code.

This document proposes an architectural design for Release-Based and Archive-Based Updates. This feature will allow PyPluginStore to:

  1. Support environments where git is unavailable.
  2. Install stable, tagged releases from GitHub Releases (using .zip or .tar.gz assets) instead of branch tips.
  3. Support arbitrary URL-based ZIP downloads for non-GitHub hosted plugins.

2. Proposed Architecture

A. Extended Registry Schema (registry.json)

To maintain backward compatibility, the registry schema can be optionally extended with an update strategy block:

"Domoticz-Plugin-Name": [
    "mario-peters",
    "Domoticz-Home-Connect-Plugin",
    "Plugin description",
    "master",
    "",
    ["linux", "windows"],
    {
        "strategy": "github_release", // "git" | "github_release" | "zip"
        "asset_pattern": ".*\\.zip",  // optional regex for assets
        "zip_url": "https://..."      // optional, required only if strategy is "zip"
    }
]

B. State Management (.pypluginstore.json)

Since archive-based installations lack a .git folder, we must track metadata locally to check for updates. We will introduce a lightweight .pypluginstore.json file inside each installed plugin's folder:

{
    "plugin_key": "Domoticz-Home-Connect-Plugin",
    "installed_at": "2026-06-29T12:00:00Z",
    "version": "1.2.0",
    "strategy": "github_release",
    "archive_url": "https://github.com/.../v1.2.0.zip"
}

C. Backend Strategy Pattern (plugin_core.py)

We will refactor the installation and update mechanics in plugin_core.py to use a Strategy Pattern:

class PluginUpdater:
    def check_update(self, plugin_key, local_path) -> tuple[str, str]: # status, latest_version
        pass
    def update(self, plugin_key, local_path) -> tuple[bool, str]: # success, message
        pass

class GitPluginUpdater(PluginUpdater):
    # Implements current git-fetch & git-rebase logic
    pass

class ArchivePluginUpdater(PluginUpdater):
    # Implements zip download, extraction, and atomic directory replacement
    pass

D. Safe, Atomic Installation & Rollback

To ensure a failed zip extraction or missing plugin.py doesn't corrupt a running plugin, the update process will follow an atomic replacement cycle:

  1. Download: Download the zip asset into a temporary directory (e.g., /tmp/pypluginstore-downloads/).
  2. Extract & Validate: Extract the ZIP. Ensure it contains a valid plugin.py with standard metadata.
  3. Backup: Move the existing plugin directory to plugins/{key}.backup/.
  4. Deploy: Move the extracted folder to plugins/{key}/.
  5. Write Metadata: Save .pypluginstore.json inside the new folder.
  6. Cleanup/Rollback:
    • If successful: Delete plugins/{key}.backup/.
    • If failed: Restore plugins/{key}.backup/ to plugins/{key}/ and log the failure.

3. Workflow & Benefits

  • No Git Dependency: Users can run PyPluginStore on lightweight embedded systems or restricted containers without needing git$.
  • Stability: Users only get tagged, validated releases rather than untested commits to master/main.
  • Efficiency: Downloading a zipped archive is significantly faster and uses less bandwidth than cloning a git repository with deep history.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions