fix(plugin): bundle Python dependencies for standalone deployment
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user