From ce0d9d83558363b0f4469e9492b3604d3c309af1 Mon Sep 17 00:00:00 2001 From: imSp4rky Date: Sun, 7 Jun 2026 15:59:59 -0600 Subject: [PATCH] feat(vault): add VAULT_TAG to share key vault across services Lets sibling services read/write one key-vault namespace instead of being tied to their own tag. New Service.VAULT_TAG class var, resolved via Services.get_vault_tag() in both the dl and serve paths. AMZN_WEB opts into the AMZN namespace; EXAMPLE documents usage. --- unshackle/commands/dl.py | 5 +++-- unshackle/core/api/handlers.py | 3 ++- unshackle/core/service.py | 2 ++ unshackle/core/services.py | 14 ++++++++++++++ unshackle/services/EXAMPLE/__init__.py | 3 +++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 1829678..0f12eba 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -618,6 +618,7 @@ class dl: ) self.service = Services.get_tag(ctx.invoked_subcommand) + self.vault_service = Services.get_vault_tag(self.service) service_dl_config = config.services.get(self.service, {}).get("dl", {}) if service_dl_config: param_types = {param.name: param.type for param in ctx.command.params if param.name} @@ -818,7 +819,7 @@ class dl: cdm_only = ctx.params.get("cdm_only") if cdm_only: - self.vaults = Vaults(self.service) + self.vaults = Vaults(self.vault_service) self.log.info("CDM-only mode: Skipping vault loading") if self.debug_logger: self.debug_logger.log( @@ -829,7 +830,7 @@ class dl: ) else: with console.status("Loading Key Vaults...", spinner="dots"): - self.vaults = Vaults(self.service) + self.vaults = Vaults(self.vault_service) total_vaults = len(config.key_vaults) failed_vaults = [] diff --git a/unshackle/core/api/handlers.py b/unshackle/core/api/handlers.py index 731f2aa..fac018b 100644 --- a/unshackle/core/api/handlers.py +++ b/unshackle/core/api/handlers.py @@ -2009,9 +2009,10 @@ def _resolve_device_name(user_config: dict, drm_type: str, service_tag: str = "" def _load_server_vaults(service_name: str) -> Any: """Load server vaults from config.key_vaults.""" from unshackle.core.config import config as app_config + from unshackle.core.services import Services from unshackle.core.vaults import Vaults - vaults = Vaults(service_name) + vaults = Vaults(Services.get_vault_tag(service_name)) for vault_config in app_config.key_vaults: cfg = vault_config.copy() vault_type = cfg.pop("type", None) diff --git a/unshackle/core/service.py b/unshackle/core/service.py index 3f965ef..5c252da 100644 --- a/unshackle/core/service.py +++ b/unshackle/core/service.py @@ -93,6 +93,8 @@ class Service(metaclass=ABCMeta): # Abstract class variables ALIASES: tuple[str, ...] = () # list of aliases for the service; alternatives to the service tag. GEOFENCE: tuple[str, ...] = () # list of ip regions required to use the service. empty list == no specific region. + # vault namespace override; when set, key vault read/write uses this tag instead of the service's own. + VAULT_TAG: Optional[str] = None def __init__(self, ctx: click.Context): console.print(Padding(Rule(f"[rule.text]Service: {self.__class__.__name__}"), (1, 2))) diff --git a/unshackle/core/services.py b/unshackle/core/services.py index 7713eca..0f60023 100644 --- a/unshackle/core/services.py +++ b/unshackle/core/services.py @@ -274,5 +274,19 @@ class Services(click.Group): raise KeyError(f"There is no Service added by the Tag '{tag}'") + @staticmethod + def get_vault_tag(name: str) -> str: + """Resolve the key-vault namespace tag for a service. + + Returns the service's VAULT_TAG override when set, otherwise its own tag. + Falls back to the resolved tag for non-local services (remote/import). + """ + tag = Services.get_tag(name) + try: + service = Services.load(tag) + except KeyError: + return tag + return getattr(service, "VAULT_TAG", None) or tag + __all__ = ("Services",) diff --git a/unshackle/services/EXAMPLE/__init__.py b/unshackle/services/EXAMPLE/__init__.py index b38c2e4..ff5894d 100644 --- a/unshackle/services/EXAMPLE/__init__.py +++ b/unshackle/services/EXAMPLE/__init__.py @@ -75,6 +75,9 @@ class EXAMPLE(Service): TITLE_RE = r"^(?:https?://(?:www\.)?domain\.com/details/)?(?P[^/?#]+)" # NO_SUBTITLES: service-level idiom telling the pipeline subs are handled in-band. NO_SUBTITLES = False + # VAULT_TAG: store/read keys under a different vault namespace than this service's tag. + # Lets sibling services share one key vault. Omit to use the service's own tag. + VAULT_TAG = "DIFFERENT_NAME" # Map our API's range strings <-> unshackle's Video.Range enum. VIDEO_RANGE_MAP = {