fix(plugin): bundle Python dependencies for standalone deployment

This commit is contained in:
2026-08-26 11:53:59 +02:00
parent e4a93cdc12
commit 0055397ed5
319 changed files with 95549 additions and 18 deletions
+48 -16
View File
@@ -9,38 +9,64 @@ import sys
import time
from typing import Optional
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
plugin_dir = os.path.dirname(os.path.abspath(__file__))
py_modules_dir = os.path.join(plugin_dir, "py_modules")
sys.path.insert(0, py_modules_dir)
sys.path.insert(0, plugin_dir)
def _ensure_deps():
try:
import cryptography # noqa: F401
return
except ImportError:
pass
import importlib.util
cffi_path = os.path.join(py_modules_dir, "_cffi_backend.cpython-311-x86_64-linux-gnu.so")
if os.path.exists(cffi_path) and "_cffi_backend" not in sys.modules:
spec = importlib.util.spec_from_file_location("_cffi_backend", cffi_path)
mod = importlib.util.module_from_spec(spec)
sys.modules["_cffi_backend"] = mod
spec.loader.exec_module(mod)
_ensure_deps()
import decky_plugin
from bitwarden_client import BitwardenClient
from crypto import BitwardenCrypto, PinCrypto
from totp import generate_totp, get_totp_remaining_seconds
from clipboard import copy_to_clipboard, clear_all_clipboards
class Plugin:
"""Main plugin class for Decky Vaultwarden."""
def __init__(self):
self.client: Optional[BitwardenClient] = None
self.pin_crypto = PinCrypto()
self.crypto = BitwardenCrypto()
self._vault_data: Optional[dict] = None
self._decrypted_vault: Optional[dict] = None
self._master_key: Optional[bytes] = None
self._enc_key: Optional[bytes] = None
self._mac_key: Optional[bytes] = None
self._session_active: bool = False
self.client = None
self.client_class = None
self.pin_crypto = None
self.crypto = None
self._vault_data = None
self._decrypted_vault = None
self._master_key = None
self._enc_key = None
self._mac_key = None
self._session_active = False
self._settings_path = os.path.join(
os.path.expanduser("~"), ".config", "decky-vaultwarden"
)
self._pin_attempts = 0
self._MAX_PIN_ATTEMPTS = 5
self._deps_ready = False
# ===== Lifecycle Methods =====
async def _main(self):
"""Called when the plugin is loaded."""
from bitwarden_client import BitwardenClient
from crypto import BitwardenCrypto, PinCrypto
self.client_class = BitwardenClient
self.pin_crypto = PinCrypto()
self.crypto = BitwardenCrypto()
self._deps_ready = True
decky_plugin.logger.info("Decky Vaultwarden plugin loaded")
os.makedirs(self._settings_path, exist_ok=True)
@@ -60,7 +86,7 @@ class Plugin:
) -> dict:
"""Login with email and master password."""
try:
self.client = BitwardenClient(server_url)
self.client = self.client_class(server_url)
result = await self.client.login_password(email, password, two_factor_token)
if result.get("two_factor_required"):
@@ -88,7 +114,7 @@ class Plugin:
) -> dict:
"""Login with API key."""
try:
self.client = BitwardenClient(server_url)
self.client = self.client_class(server_url)
result = await self.client.login_api_key(client_id, client_secret, email)
if result.get("success"):
@@ -197,6 +223,7 @@ class Plugin:
async def copy_password(self, cipher_id: str) -> dict:
"""Copy a password to clipboard with 60s auto-clear."""
from clipboard import copy_to_clipboard
password = self._get_cipher_field(cipher_id, "password")
if password is None:
return {"success": False, "error": "Password not found"}
@@ -204,6 +231,7 @@ class Plugin:
async def copy_username(self, cipher_id: str) -> dict:
"""Copy a username to clipboard with 60s auto-clear."""
from clipboard import copy_to_clipboard
username = self._get_cipher_field(cipher_id, "username")
if username is None:
return {"success": False, "error": "Username not found"}
@@ -211,6 +239,8 @@ class Plugin:
async def copy_totp(self, cipher_id: str) -> dict:
"""Copy TOTP code to clipboard with 60s auto-clear."""
from clipboard import copy_to_clipboard
from totp import generate_totp, get_totp_remaining_seconds
totp_secret = self._get_cipher_field(cipher_id, "totp")
if totp_secret:
code = generate_totp(totp_secret)
@@ -222,6 +252,7 @@ class Plugin:
async def get_totp_code(self, cipher_id: str) -> dict:
"""Get current TOTP code without copying."""
from totp import generate_totp, get_totp_remaining_seconds
totp_secret = self._get_cipher_field(cipher_id, "totp")
if totp_secret:
code = generate_totp(totp_secret)
@@ -346,6 +377,7 @@ class Plugin:
async def lock_vault(self) -> dict:
"""Lock the vault and clear sensitive data."""
from clipboard import clear_all_clipboards
await clear_all_clipboards()
self._decrypted_vault = None
self._vault_data = None