diff --git a/.env.example b/.env.example
index 5436967..c5e52ba 100644
--- a/.env.example
+++ b/.env.example
@@ -5,9 +5,12 @@
# Version: 3.0.0
# Last Updated: 2026-02-17
#
-# This file contains all environment variables needed to run the MCP server.
+# This file contains environment variables needed to run the MCP server.
# Copy this file to .env and fill in your actual values.
#
+# SITES: Sites are managed via the web dashboard (DB-based), not env vars.
+# The plugin sections below are kept as credential reference only.
+#
# SECURITY NOTE: Never commit .env file to version control!
#
# Multi-Endpoint Architecture (v3.0.0):
diff --git a/CLAUDE.md b/CLAUDE.md
index b7cd90f..1051ef5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -125,26 +125,17 @@ plugins/{name}/
### Tool Generation
-Tools are dynamically generated at startup:
-1. `SiteManager` discovers sites from env vars (`{PLUGIN_TYPE}_{SITE_ID}_{CONFIG_KEY}`)
-2. `ToolGenerator` creates unified tools with a `site` parameter injected
-3. Tools are registered in `ToolRegistry` and exposed via FastMCP
+Tools are dynamically generated at startup from plugin specifications:
+1. `ToolGenerator` creates unified tools with a `site` parameter injected
+2. Tools are registered in `ToolRegistry` and exposed via FastMCP
Unified tool pattern: `wordpress_create_post(site="myblog", title="Hello")` — the `site` parameter accepts either a site_id or alias.
-### Site Configuration via Environment Variables
+### Site Configuration
-Pattern: `{PLUGIN_TYPE}_{SITE_ID}_{CONFIG_KEY}`
-
-```bash
-WORDPRESS_SITE1_URL=https://example.com
-WORDPRESS_SITE1_USERNAME=admin
-WORDPRESS_SITE1_APP_PASSWORD=xxxx xxxx xxxx xxxx
-WORDPRESS_SITE1_ALIAS=myblog # optional friendly name
-WORDPRESS_SITE1_CONTAINER=wp-docker # optional, for WP-CLI
-```
-
-Parsed by `core/site_manager.py` into `SiteConfig` (Pydantic model). Sites are auto-discovered on startup.
+Sites are managed via the web dashboard and stored in SQLite (DB-based).
+User sites are encrypted with AES-256-GCM and accessed via `core/site_api.py` + `core/database.py`.
+`SiteManager` (`core/site_manager.py`) provides registration and lookup infrastructure for tool generation.
### Key Core Modules
@@ -152,7 +143,7 @@ Parsed by `core/site_manager.py` into `SiteConfig` (Pydantic model). Sites are a
|--------|---------|
| `core/auth.py` | Master API key validation, request authentication |
| `core/api_keys.py` | Per-project API keys with scopes (read/write/admin) |
-| `core/site_manager.py` | Type-safe site config discovery from env vars |
+| `core/site_manager.py` | Type-safe site config registration and lookup |
| `core/tool_registry.py` | Central tool definitions and lookup |
| `core/tool_generator.py` | Dynamic unified tool creation with site injection |
| `core/health.py` | Health monitoring, metrics, alerts |
@@ -182,7 +173,7 @@ Web UI at the server root, built with Starlette + Jinja2 + HTMX + Tailwind CSS.
### Legacy Modules (Deprecated)
-`core/project_manager.py`, `core/site_registry.py`, `core/unified_tools.py` — kept for backward compatibility. New code should use `SiteManager`, `ToolRegistry`, and `ToolGenerator` instead.
+`core/project_manager.py` (retained for HealthMonitor compatibility), `core/site_registry.py`, `core/unified_tools.py` — kept for backward compatibility. New code should use `SiteManager`, `ToolRegistry`, and `ToolGenerator` instead.
## Commit Style
@@ -211,7 +202,7 @@ v4 development cycle with 15 phases. See `docs/ROADMAP.md` (Track F) and `.claud
- `env.example` has "FUTURE" labels for Supabase/Gitea but both are fully implemented
- 4 plugins are tested for public use: WordPress, WooCommerce, Supabase, OpenPanel. Others are admin-only or disabled.
- OAuth Clients (Client ID/Secret) are for MCP endpoint auth, NOT the same as GitHub/Google dashboard login
-- User sites stored in SQLite (`core/database.py`), admin sites still from env vars
+- All sites stored in SQLite (`core/database.py`), managed via web dashboard
- Dashboard templates live in `core/templates/` (included in pip package as `package_data`)
- `ruff` config uses top-level `select` key in pyproject.toml (not `[tool.ruff.lint]` nested format)
- The `scripts/` directory has platform-specific setup scripts: `setup.sh` (Linux/Mac), `setup.ps1` (Windows)
diff --git a/README.md b/README.md
index 44b4658..93762c6 100644
--- a/README.md
+++ b/README.md
@@ -125,28 +125,15 @@ Your personal MCP endpoint: `https://mcp.example.com/u/{your-user-id}/{alias}/mc
### Configure Your Sites
-Add site credentials to `.env`:
+Sites are managed via the **web dashboard** — no environment variables needed.
+
+1. Set `MASTER_API_KEY` in your `.env` file
+2. Start the server and open the dashboard
+3. Add sites with their credentials (URL, username, password/token)
```bash
-# Master API Key (recommended — auto-generates temp key if omitted)
+# .env — only system configuration needed
MASTER_API_KEY=your-secure-key-here
-
-# WordPress Site
-WORDPRESS_SITE1_URL=https://myblog.com
-WORDPRESS_SITE1_USERNAME=admin
-WORDPRESS_SITE1_APP_PASSWORD=xxxx xxxx xxxx xxxx
-WORDPRESS_SITE1_ALIAS=myblog
-
-# WooCommerce Store
-WOOCOMMERCE_STORE1_URL=https://mystore.com
-WOOCOMMERCE_STORE1_CONSUMER_KEY=ck_xxxxx
-WOOCOMMERCE_STORE1_CONSUMER_SECRET=cs_xxxxx
-WOOCOMMERCE_STORE1_ALIAS=mystore
-
-# Gitea Instance
-GITEA_REPO1_URL=https://git.example.com
-GITEA_REPO1_TOKEN=your_gitea_token
-GITEA_REPO1_ALIAS=mygitea
```
{% if lang == 'fa' %} نکته: فقط آدرس URL کافی است. هنگام اتصال میتوانید با API Key یا GitHub/Google احراز هویت کنید. - ساخت OAuth Client اختیاری است. + ساخت OAuth Client اختیاری است. {% else %} Tip: You only need the URL above. When connecting, you can authenticate with an API Key or GitHub/Google. - Creating an OAuth Client is optional. + Creating an OAuth Client is optional. {% endif %}
diff --git a/core/templates/dashboard/projects/list.html b/core/templates/dashboard/projects/list.html index b500d6c..7b01d0c 100644 --- a/core/templates/dashboard/projects/list.html +++ b/core/templates/dashboard/projects/list.html @@ -228,6 +228,14 @@{% if lang == 'fa' %}فیلترها را پاک کنید{% else %}Try clearing the filters{% endif %}
+ {% else %} ++ {% if lang == 'fa' %} + سایتها از طریق صفحه سایتها مدیریت میشوند. + {% else %} + Sites are managed via the My Sites page. + {% endif %} +
{% endif %} diff --git a/docker-compose.coolify.yaml b/docker-compose.coolify.yaml index 69c617a..f67d180 100644 --- a/docker-compose.coolify.yaml +++ b/docker-compose.coolify.yaml @@ -50,15 +50,9 @@ services: - USER_RATE_LIMIT_PER_MIN=${USER_RATE_LIMIT_PER_MIN:-30} - USER_RATE_LIMIT_PER_HR=${USER_RATE_LIMIT_PER_HR:-500} - # === ADMIN-MANAGED SITES (optional) === - # Add WordPress/Gitea/n8n/etc. sites here if you want admin-level access. - # Users can add their own sites via the dashboard without these. - # Format: {PLUGIN_TYPE}_{SITE_ID}_{CONFIG_KEY} - # - # - WORDPRESS_SITE1_URL=${WORDPRESS_SITE1_URL:-} - # - WORDPRESS_SITE1_USERNAME=${WORDPRESS_SITE1_USERNAME:-} - # - WORDPRESS_SITE1_APP_PASSWORD=${WORDPRESS_SITE1_APP_PASSWORD:-} - # - WORDPRESS_SITE1_ALIAS=${WORDPRESS_SITE1_ALIAS:-} + # === SITES === + # Sites are managed via the web dashboard (DB-based). + # After deployment, open the dashboard to add sites. healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8000/health"] diff --git a/docs/getting-started.md b/docs/getting-started.md index 41f6a94..b24f959 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -248,12 +248,11 @@ Without Docker socket: - WordPress Advanced database/system tools are unavailable - All REST API tools (bulk operations, content management) work normally -### Environment Variable Reference +### Site Configuration -All site configuration follows the pattern: `{PLUGIN_TYPE}_{SITE_ID}_{CONFIG_KEY}=value` +Sites are managed via the web dashboard. After starting the server, open the dashboard to add sites. -- `SITE_ID` can be any alphanumeric identifier (e.g., `SITE1`, `PROD`, `MYBLOG`) -- Multiple sites: change `SITE1` to `SITE2`, `SITE3`, etc. +Each plugin requires specific credentials (see below for reference). #### WordPress ```env @@ -392,7 +391,7 @@ docker compose logs -f mcphub | Port 8000 already in use | Change port in docker-compose.yaml: `"8001:8000"` | | Health check shows "unhealthy" | Wait 60 seconds, then check logs for startup errors | | Dashboard login fails | Make sure you're using the `MASTER_API_KEY` value from your `.env` | -| Sites not showing up | Restart after adding new env vars: `docker compose restart` | +| Sites not showing up | Add sites via the web dashboard, then check the connect page | --- @@ -610,18 +609,16 @@ docker compose up --build -d ### Step 3: Configure Environment Variables -Add all required environment variables in Coolify's environment variable UI: +Add the required environment variables in Coolify's environment variable UI: ``` MASTER_API_KEY=your-secure-key-here OAUTH_JWT_SECRET_KEY=your-jwt-secret OAUTH_BASE_URL=https://your-domain.com -WORDPRESS_SITE1_URL=https://example.com -WORDPRESS_SITE1_USERNAME=admin -WORDPRESS_SITE1_APP_PASSWORD=xxxx xxxx xxxx xxxx +ENCRYPTION_KEY=your-base64-encryption-key ``` -The server auto-discovers all `WORDPRESS_*`, `WOOCOMMERCE_*`, `GITEA_*`, and other plugin environment variables at startup. +After deployment, add sites via the web dashboard at `https://your-domain.com/dashboard`. ### Step 4: Configure Health Check diff --git a/env.example b/env.example index 8e6f9c2..6fa7d63 100644 --- a/env.example +++ b/env.example @@ -2,7 +2,7 @@ # MCP Hub — Environment Configuration # =================================== # -# MINIMUM TO START: Just set MASTER_API_KEY and one site below. +# MINIMUM TO START: Set MASTER_API_KEY, then add sites via the web dashboard. # Full docs: https://github.com/airano-ir/mcphub/blob/main/docs/getting-started.md # # After editing, run: @@ -49,117 +49,14 @@ DASHBOARD_SESSION_SECRET= # DISABLE_MASTER_KEY_LOGIN=false # ============================================ -# WORDPRESS SITES +# SITES # ============================================ -# Pattern: WORDPRESS_{SITE_ID}_{CONFIG_KEY} -# Add as many sites as you want (SITE1, SITE2, SITE3, ...) +# Sites are managed via the web dashboard (DB-based). +# After starting the server, open the dashboard to add sites: +# http://localhost:8000/dashboard # -# How to create a WordPress Application Password: -# 1. Log into WordPress admin → Users → Your Profile -# 2. Scroll to "Application Passwords" section -# 3. Enter name "MCP Hub" → click "Add New" -# 4. Copy the generated password (format: xxxx xxxx xxxx xxxx) - -WORDPRESS_SITE1_URL=https://your-site.com -WORDPRESS_SITE1_USERNAME=admin -WORDPRESS_SITE1_APP_PASSWORD=xxxx xxxx xxxx xxxx -WORDPRESS_SITE1_ALIAS=mysite - -# Optional: Docker container name for WP-CLI tools (cache flush, transients, etc.) -# Find your container name with: docker ps --filter name=wordpress -# This lets MCP Hub run wp-cli commands inside your WordPress container. -# WORDPRESS_SITE1_CONTAINER=wordpress-container-name - -# Second site example (uncomment to use): -# WORDPRESS_SITE2_URL=https://another-site.com -# WORDPRESS_SITE2_USERNAME=admin -# WORDPRESS_SITE2_APP_PASSWORD=yyyy yyyy yyyy yyyy -# WORDPRESS_SITE2_ALIAS=blog - -# ============================================ -# WORDPRESS ADVANCED (database, bulk, system) -# ============================================ -# Pattern: WORDPRESS_ADVANCED_{SITE_ID}_{CONFIG_KEY} -# Provides database operations, bulk operations, and system management. -# REQUIRES Docker socket AND container name — all tools use WP-CLI. -# -# Uses the same credentials as WordPress, but the CONTAINER key is REQUIRED. -# WORDPRESS_ADVANCED_SITE1_URL=https://your-site.com -# WORDPRESS_ADVANCED_SITE1_USERNAME=admin -# WORDPRESS_ADVANCED_SITE1_APP_PASSWORD=xxxx xxxx xxxx xxxx -# WORDPRESS_ADVANCED_SITE1_CONTAINER=wordpress-container-name -# WORDPRESS_ADVANCED_SITE1_ALIAS=mysite-advanced - -# ============================================ -# WOOCOMMERCE STORES -# ============================================ -# Pattern: WOOCOMMERCE_{STORE_ID}_{CONFIG_KEY} -# WooCommerce is a separate plugin — uses Consumer Key/Secret, not App Passwords. -# -# Create keys: WooCommerce → Settings → Advanced → REST API → Add Key - -# WOOCOMMERCE_STORE1_URL=https://your-store.com -# WOOCOMMERCE_STORE1_CONSUMER_KEY=ck_xxxxx -# WOOCOMMERCE_STORE1_CONSUMER_SECRET=cs_xxxxx -# WOOCOMMERCE_STORE1_ALIAS=mystore - -# ============================================ -# OTHER PLUGINS (uncomment to use) -# ============================================ -# All plugins auto-discover their environment variables at startup. -# Pattern: {PLUGIN_TYPE}_{INSTANCE_ID}_{CONFIG_KEY} - -# --- Gitea --- -# GITEA_REPO1_URL=https://git.example.com -# GITEA_REPO1_TOKEN=your_gitea_token -# GITEA_REPO1_ALIAS=mygitea - -# --- n8n --- -# N8N_INSTANCE1_URL=https://n8n.example.com -# N8N_INSTANCE1_API_KEY=your_n8n_api_key -# N8N_INSTANCE1_ALIAS=myn8n - -# --- Supabase (Self-Hosted or Cloud) --- -# Works with both self-hosted Supabase and supabase.com cloud projects. -# For cloud: URL is https://xxxx.supabase.co -# For self-hosted: URL is your Kong gateway (e.g., http://your-server:8000) -# -# SERVICE_ROLE_KEY is required (bypasses RLS, full admin access). -# ANON_KEY is optional — if omitted, SERVICE_ROLE_KEY is used for all calls. -# -# Note: postgres-meta tools (list_tables, execute_sql, get_table_schema, etc.) -# require the /pg/ Kong route or a direct META_URL. These tools do NOT work -# on supabase.com cloud (postgres-meta is internal only on cloud). -# PostgREST/Auth/Storage/Functions tools work on both cloud and self-hosted. -# -# SUPABASE_PROJECT1_URL=https://xxxx.supabase.co -# SUPABASE_PROJECT1_SERVICE_ROLE_KEY=your_service_role_key -# SUPABASE_PROJECT1_ANON_KEY=your_anon_key -# SUPABASE_PROJECT1_ALIAS=mysupabase -# -# Optional: direct postgres-meta URL for self-hosted when /pg/ is not via Kong: -# SUPABASE_PROJECT1_META_URL=http://localhost:5555 -# -# Optional: Basic Auth for postgres-meta when exposed via a public URL. -# Format: username:password — not needed for internal Docker networks or Kong. -# SUPABASE_PROJECT1_META_AUTH=admin:secretpassword - -# --- OpenPanel --- -# OPENPANEL_INSTANCE1_URL=https://openpanel.example.com -# OPENPANEL_INSTANCE1_CLIENT_ID=your_client_id -# OPENPANEL_INSTANCE1_CLIENT_SECRET=your_client_secret -# OPENPANEL_INSTANCE1_ALIAS=myopenpanel - -# --- Appwrite --- -# APPWRITE_PROJECT1_URL=https://appwrite.example.com -# APPWRITE_PROJECT1_API_KEY=your_appwrite_api_key -# APPWRITE_PROJECT1_PROJECT_ID=your_project_id -# APPWRITE_PROJECT1_ALIAS=myappwrite - -# --- Directus --- -# DIRECTUS_INSTANCE1_URL=https://directus.example.com -# DIRECTUS_INSTANCE1_TOKEN=your_directus_token -# DIRECTUS_INSTANCE1_ALIAS=mydirectus +# Supported plugins: WordPress, WooCommerce, Supabase, OpenPanel, +# Gitea, n8n, Appwrite, Directus, WordPress Advanced # ============================================ # OAUTH (optional — for ChatGPT/Claude auto-registration) diff --git a/plugins/openpanel/handlers/export.py b/plugins/openpanel/handlers/export.py index 232c380..98e69ea 100644 --- a/plugins/openpanel/handlers/export.py +++ b/plugins/openpanel/handlers/export.py @@ -4,8 +4,8 @@ Uses REST APIs: - Export API (GET /export/events, /export/charts) for raw data export - Insights API (GET /insights/:projectId/*) for analytics queries -Note: project_id is optional if configured in environment variables. -When not provided, the default project_id from OPENPANEL_SITE1_PROJECT_ID is used. +Note: project_id is optional if configured in the site settings. +When not provided, the default project_id from the site configuration is used. Requires 'read' or 'root' mode client for Export API. """ diff --git a/plugins/openpanel/handlers/utils.py b/plugins/openpanel/handlers/utils.py index 57b8722..bab15cf 100644 --- a/plugins/openpanel/handlers/utils.py +++ b/plugins/openpanel/handlers/utils.py @@ -23,6 +23,6 @@ def get_project_id(client: OpenPanelClient, project_id: str | None) -> str: return client.default_project_id raise ValueError( "project_id is required. Either provide it as a parameter or configure " - "OPENPANEL_SITE1_PROJECT_ID in environment variables. " + "the project_id field when adding the site in the dashboard. " "You can find your Project ID in OpenPanel Dashboard → Project Settings." ) diff --git a/plugins/supabase/handlers/auth.py b/plugins/supabase/handlers/auth.py index a4bb174..d657e5e 100644 --- a/plugins/supabase/handlers/auth.py +++ b/plugins/supabase/handlers/auth.py @@ -483,6 +483,8 @@ async def delete_user_factor(client: SupabaseClient, user_id: str, factor_id: st return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False) + + async def search_users( client: SupabaseClient, query: str, page: int = 1, per_page: int = 50 ) -> str: diff --git a/plugins/wordpress/client.py b/plugins/wordpress/client.py index 185ccde..be3592d 100644 --- a/plugins/wordpress/client.py +++ b/plugins/wordpress/client.py @@ -67,18 +67,17 @@ class WordPressClient: # Validate required parameters if not site_url: raise ConfigurationError( - "Site URL is not configured. " - "Please set the URL environment variable (e.g., WORDPRESS_SITE1_URL)." + "Site URL is not configured. " "Please add or update the site in the dashboard." ) if not username: raise ConfigurationError( "Username is not configured. " - "Please set the USERNAME environment variable (e.g., WORDPRESS_SITE1_USERNAME)." + "Please update the site credentials in the dashboard." ) if not app_password: raise ConfigurationError( "App password is not configured. " - "Please set the APP_PASSWORD environment variable (e.g., WORDPRESS_SITE1_APP_PASSWORD)." + "Please update the site credentials in the dashboard." ) self.site_url = site_url.rstrip("/") diff --git a/plugins/wordpress/wp_cli.py b/plugins/wordpress/wp_cli.py index 5b263f4..30493b3 100644 --- a/plugins/wordpress/wp_cli.py +++ b/plugins/wordpress/wp_cli.py @@ -77,12 +77,8 @@ class WPCLIManager: try: # First, test if we have Docker socket access test_process = await asyncio.create_subprocess_exec( - "docker", - "version", - "--format", - "{{.Server.Version}}", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + "docker", "version", "--format", "{{.Server.Version}}", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) test_stdout, test_stderr = await asyncio.wait_for( @@ -103,15 +99,10 @@ class WPCLIManager: # Now check for our specific container using exact name match process = await asyncio.create_subprocess_exec( - "docker", - "ps", - "--all", - "--filter", - f"name=^{self.container_name}$", - "--format", - "{{.Names}}|{{.Status}}", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + "docker", "ps", "--all", + "--filter", f"name=^{self.container_name}$", + "--format", "{{.Names}}|{{.Status}}", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=5.0) @@ -126,13 +117,8 @@ class WPCLIManager: if not output: # Container not found - get list of available containers for helpful error list_process = await asyncio.create_subprocess_exec( - "docker", - "ps", - "--all", - "--format", - "{{.Names}}", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + "docker", "ps", "--all", "--format", "{{.Names}}", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) list_stdout, _ = await list_process.communicate() available = list_stdout.decode().strip().split("\n") if list_stdout else [] @@ -184,14 +170,9 @@ class WPCLIManager: try: # Try to run wp --version process = await asyncio.create_subprocess_exec( - "docker", - "exec", - self.container_name, - "wp", - "--version", - "--allow-root", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + "docker", "exec", self.container_name, + "wp", "--version", "--allow-root", + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=5.0) @@ -310,9 +291,7 @@ class WPCLIManager: ) # 4. Build docker exec command (split command into args to prevent shell injection) - cmd_parts = ( - ["docker", "exec", self.container_name, "wp"] + command.split() + ["--allow-root"] - ) + cmd_parts = ["docker", "exec", self.container_name, "wp"] + command.split() + ["--allow-root"] self.logger.info(f"Executing: {' '.join(cmd_parts)}") @@ -603,29 +582,17 @@ class WPCLIManager: try: # Try GNU stat first, fall back to BSD stat process = await asyncio.create_subprocess_exec( - "docker", - "exec", - self.container_name, - "stat", - "-c", - "%s", - export_path, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + "docker", "exec", self.container_name, + "stat", "-c", "%s", export_path, + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, _ = await asyncio.wait_for(process.communicate(), timeout=5.0) if process.returncode != 0: # BSD stat fallback process = await asyncio.create_subprocess_exec( - "docker", - "exec", - self.container_name, - "stat", - "-f", - "%z", - export_path, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + "docker", "exec", self.container_name, + "stat", "-f", "%z", export_path, + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, _ = await asyncio.wait_for(process.communicate(), timeout=5.0) diff --git a/plugins/wordpress_advanced/plugin.py b/plugins/wordpress_advanced/plugin.py index 7e3b268..0560228 100644 --- a/plugins/wordpress_advanced/plugin.py +++ b/plugins/wordpress_advanced/plugin.py @@ -67,7 +67,7 @@ class WordPressAdvancedPlugin(BasePlugin): if not container_name: raise ValueError( "WordPress Advanced plugin requires 'container' configuration. " - "Please set WORDPRESS_ADVANCED_SITE1_CONTAINER in environment variables." + "Please set the 'container' field when adding the site in the dashboard." ) # Import WP-CLI manager diff --git a/server.py b/server.py index 3f77eda..ec8a852 100644 --- a/server.py +++ b/server.py @@ -14,8 +14,9 @@ Usage: Environment Variables: MASTER_API_KEY: Master API key for authentication - {PLUGIN_TYPE}_{PROJECT_ID}_{CONFIG_KEY}: Project configurations LOG_LEVEL: Logging level (DEBUG, INFO, WARNING, ERROR) + +Sites are managed via the web dashboard and stored in SQLite (DB-based). """ import base64 @@ -66,8 +67,6 @@ from core.dashboard.routes import ( api_get_config, api_list_keys, api_list_sites, - # K.5: Settings routes - api_save_setting, api_test_site, api_update_site, # E.2: OAuth Social Login routes @@ -109,6 +108,8 @@ from core.dashboard.routes import ( # F.3: Service pages dashboard_service_page, dashboard_services_list, + # K.5: Settings routes + api_save_setting, dashboard_settings_page, # E.3: Site Management pages dashboard_sites_add, @@ -308,7 +309,6 @@ from plugins import registry as plugin_registry site_manager = get_site_manager() plugin_types = plugin_registry.get_registered_types() -site_manager.discover_sites(plugin_types) # Initialize tool registry (central tool management) tool_registry = get_tool_registry() @@ -323,24 +323,8 @@ if auth_manager._is_temporary_key: logger.info(f"Master API Key (temporary): {_mk}") else: logger.info(f"Master API Key: {_mk[:8]}***{_mk[-4:]}") -logger.info(f"Discovered {len(project_manager.projects)} per-site project instances (legacy)") -logger.info( - f"Discovered {site_manager.get_count()} unique sites across {len(plugin_types)} plugin types" -) -logger.info(f"Site breakdown: {site_manager.get_count_by_type()}") - -# Log discovered projects -for full_id, plugin in project_manager.projects.items(): - logger.info(f" - {full_id} ({plugin.get_plugin_name()})") - -# Log discovered sites -logger.info("\nDiscovered sites:") -for site_info in site_manager.list_all_sites(): - alias_display = ( - f" (alias: {site_info['alias']})" if site_info["alias"] != site_info["site_id"] else "" - ) - logger.info(f" - {site_info['full_id']}{alias_display}") - +logger.info(f"Plugin types registered: {', '.join(plugin_types)}") +logger.info("Sites are managed via the web dashboard (DB-based)") logger.info("=" * 60) @@ -5032,12 +5016,6 @@ def main(): logger.info(f"Host: {args.host}") logger.info(f"Port: {args.port}") - # Check if any sites were discovered (SiteManager is the primary discovery system) - if site_manager.get_count() == 0: - logger.warning("No sites discovered! Check your environment variables.") - logger.warning("Expected format: {PLUGIN_TYPE}_{SITE_ID}_{CONFIG_KEY}=value") - logger.warning("Example: WORDPRESS_SITE1_URL=https://example.com") - try: if args.transport == "stdio": # stdio doesn't support multi-endpoint diff --git a/server_multi.py b/server_multi.py index 7020e2f..5b1b778 100644 --- a/server_multi.py +++ b/server_multi.py @@ -165,7 +165,6 @@ rate_limiter = get_rate_limiter() # Initialize site manager site_manager = get_site_manager() plugin_types = plugin_registry.get_registered_types() -site_manager.discover_sites(plugin_types) # WooCommerce can fallback to WordPress site configurations if no WOOCOMMERCE_* env vars # This mapping is only used when woocommerce has no sites configured diff --git a/tests/test_admin_system.py b/tests/test_admin_system.py index 94ad5f7..8f8b6e3 100644 --- a/tests/test_admin_system.py +++ b/tests/test_admin_system.py @@ -1,5 +1,6 @@ """Tests for Admin System Unification (F.4).""" + from core.dashboard.auth import ( DashboardSession, get_session_display_info, diff --git a/tests/test_site_manager.py b/tests/test_site_manager.py index 2ae2442..c97dadb 100644 --- a/tests/test_site_manager.py +++ b/tests/test_site_manager.py @@ -1,4 +1,4 @@ -"""Tests for Site Manager (core/site_manager.py).""" +"""Tests for Site Manager (core/site_manager.py) — registration and lookup.""" import pytest @@ -127,69 +127,6 @@ class TestSiteManagerRegistration: assert manager.list_sites("nonexistent") == [] -class TestSiteManagerDiscovery: - """Test site discovery from environment variables.""" - - def test_discover_wordpress_sites(self, monkeypatch): - """Should discover sites from WORDPRESS_SITE1_* env vars.""" - monkeypatch.setenv("WORDPRESS_SITE1_URL", "https://wp1.example.com") - monkeypatch.setenv("WORDPRESS_SITE1_USERNAME", "admin") - monkeypatch.setenv("WORDPRESS_SITE1_APP_PASSWORD", "xxxx") - monkeypatch.setenv("WORDPRESS_SITE1_ALIAS", "myblog") - - manager = SiteManager() - count = manager.discover_sites(["wordpress"]) - - assert count == 1 - config = manager.get_site_config("wordpress", "site1") - assert config.url == "https://wp1.example.com" - assert config.username == "admin" - assert config.alias == "myblog" - - def test_discover_multiple_sites(self, monkeypatch): - """Should discover multiple sites for same plugin type.""" - monkeypatch.setenv("WORDPRESS_SITE1_URL", "https://wp1.example.com") - monkeypatch.setenv("WORDPRESS_SITE1_USERNAME", "admin") - monkeypatch.setenv("WORDPRESS_SITE2_URL", "https://wp2.example.com") - monkeypatch.setenv("WORDPRESS_SITE2_USERNAME", "editor") - - manager = SiteManager() - count = manager.discover_sites(["wordpress"]) - - assert count == 2 - - def test_discover_across_plugin_types(self, monkeypatch): - """Should discover sites across different plugin types.""" - monkeypatch.setenv("WORDPRESS_SITE1_URL", "https://wp.example.com") - monkeypatch.setenv("WORDPRESS_SITE1_USERNAME", "admin") - monkeypatch.setenv("GITEA_SITE1_URL", "https://git.example.com") - monkeypatch.setenv("GITEA_SITE1_TOKEN", "tok_123") - - manager = SiteManager() - count = manager.discover_sites(["wordpress", "gitea"]) - - assert count == 2 - - def test_reserved_words_skipped(self, monkeypatch): - """Reserved words like LIMIT, RATE should not become site IDs.""" - monkeypatch.setenv("WORDPRESS_LIMIT_PER_MINUTE", "100") - monkeypatch.setenv("WORDPRESS_RATE_PER_HOUR", "500") - - manager = SiteManager() - count = manager.discover_sites(["wordpress"]) - - assert count == 0 - - def test_incomplete_config_skipped(self, monkeypatch): - """Sites with no config data (only prefix match) should be skipped.""" - # WORDPRESS_SITE1_ exists as prefix but no actual config keys - # This is hard to test directly since env vars always have a value - # Instead, test that a site with only site_id and plugin_type (no config) is skipped - manager = SiteManager() - count = manager.discover_sites(["wordpress"]) - assert count == 0 - - class TestSiteManagerCounts: """Test counting and listing methods."""