Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions acapy_agent/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from ..core.plugin_registry import PluginRegistry
from ..messaging.models.openapi import OpenAPISchema
from ..utils.plugin_installer import get_plugin_version
from ..utils.stats import Collector
from ..version import __version__
from .decorators.auth import admin_authentication
Expand Down Expand Up @@ -71,12 +72,32 @@ async def plugins_handler(request: web.BaseRequest):
request: aiohttp request object

Returns:
The module list response
The module list response with plugin names and versions

"""
registry = request.app["context"].inject_or(PluginRegistry)
plugins = registry and sorted(registry.plugin_names) or []
return web.json_response({"result": plugins})

# Get versions for external plugins only (skip built-in acapy_agent plugins)
external_plugins = []
for plugin_name in plugins:
if not plugin_name.startswith("acapy_agent."):
# External plugin - try to get version info
# Wrap in try/except to prevent failures from affecting the endpoint
try:
version_info = get_plugin_version(plugin_name) or {}
except Exception:
# If version lookup fails, just include plugin without version info
version_info = {}
external_plugins.append(
{
"name": plugin_name,
"package_version": version_info.get("package_version", None),
"source_version": version_info.get("source_version", None),
}
)

return web.json_response({"result": plugins, "external": external_plugins})


@docs(tags=["server"], summary="Fetch the server configuration")
Expand Down
44 changes: 44 additions & 0 deletions acapy_agent/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from ..config.default_context import DefaultContextBuilder
from ..config.util import common_config
from ..core.conductor import Conductor
from ..utils.plugin_installer import install_plugins_from_config
from ..version import __version__ as acapy_version
from . import PROG

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -60,6 +62,48 @@ async def run_app(argv: Sequence[str] = None):
settings = get_settings(args)
common_config(settings)

# Install plugins if auto-install is enabled and plugins are specified
external_plugins = settings.get("external_plugins", [])
if external_plugins:
auto_install = settings.get("auto_install_plugins", False)
plugin_version = settings.get("plugin_install_version")

if auto_install:
version_info = (
f"version {plugin_version}"
if plugin_version
else f"current ACA-Py version ({acapy_version})"
)
# Always print to console for visibility
plugins_str = ", ".join(external_plugins)
print(
f"Auto-installing plugins from acapy-plugins repository: "
f"{plugins_str} ({version_info})"
)
LOGGER.info(
"Auto-installing plugins from acapy-plugins repository: %s (%s)",
", ".join(external_plugins),
version_info,
)

failed_plugins = install_plugins_from_config(
plugin_names=external_plugins,
auto_install=auto_install,
plugin_version=plugin_version,
)

if failed_plugins:
LOGGER.error(
"Failed to install the following plugins: %s",
", ".join(failed_plugins),
)
LOGGER.error(
"Please ensure these plugins are available in the "
"acapy-plugins repository or install them manually before "
"starting ACA-Py."
)
sys.exit(1)

# Set ledger to read-only if explicitly specified
settings["ledger.read_only"] = settings.get("read_only_ledger", False)

Expand Down
37 changes: 37 additions & 0 deletions acapy_agent/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,23 @@ def add_arguments(self, parser: ArgumentParser):
),
)

parser.add_argument(
"--auto-install-plugins",
dest="auto_install_plugins",
nargs="?",
const=True,
default=False,
metavar="<version>",
env_var="ACAPY_AUTO_INSTALL_PLUGINS",
help=(
"Automatically install missing plugins from the "
"acapy-plugins repository. If specified without a value, uses "
"current ACA-Py version. If a version is provided (e.g., 1.3.2), "
"uses that version for plugin installation. "
"Default: false (disabled)."
),
)

parser.add_argument(
"--storage-type",
type=str,
Expand Down Expand Up @@ -803,6 +820,26 @@ def get_settings(self, args: Namespace) -> dict:
reduce(lambda v, k: {k: v}, key.split(".")[::-1], value),
)

# Auto-install plugins: can be True (use current version),
# version string (e.g., "1.3.2"), or False
if hasattr(args, "auto_install_plugins"):
auto_install_value = args.auto_install_plugins
if auto_install_value is True:
# Flag present without value - use current ACA-Py version
settings["auto_install_plugins"] = True
settings["plugin_install_version"] = None # Use current version
elif isinstance(auto_install_value, str):
# Flag present with version value
settings["auto_install_plugins"] = True
settings["plugin_install_version"] = auto_install_value
else:
# False or None - disabled
settings["auto_install_plugins"] = False
settings["plugin_install_version"] = None
else:
settings["auto_install_plugins"] = False
settings["plugin_install_version"] = None

if args.storage_type:
settings["storage_type"] = args.storage_type

Expand Down
Loading
Loading