After /release-deleted deactivates an extension (when the last release
is removed from Gitea), running /sync-registry would reactivate it
because the repo still exists. Now sync-registry skips reactivation
when an extension has zero rows in extension_versions — those were
intentionally retired and must stay deactivated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manifest is now the source of truth for the catalog label shown in the
admin UI. build_and_publish updates description on every release; the
new _sync_description_from_manifest helper is also called from
sync_extension_registry for backfill / drift correction (runs every 6h
via the auto-add-webhook workflow, or on manual /sync-registry).
This replaces the hardcoded HEBREW_LABELS map in KlearBranding's
admin/index.js — new extensions inherit their label from their manifest
without code changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a release is deleted in Gitea (e.g., to retire an extension), the
platform DB still showed the extension as installable. This endpoint
removes the version row, recomputes latest_version, and deactivates
the extension if no versions remain — making it disappear from the
install catalog without touching the repo.
Called by n8n workflow NwrRbHNAXFVOLHCQ on Gitea release.deleted event.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the system used simple string inequality to detect available
updates, causing downgrades to appear as upgrades (e.g. 2.5.1 → 2.5.0).
Now both backend (SQL) and frontend (JS) compare version tuples properly.
Also fixes latest_version in DB on registry sync startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Commit fed6ca3 replaced the entire server.py (1017 lines) with a single
${FILE:/tmp/ext-platform-server.py} placeholder via Gitea API, breaking
the application. This restores the correct code and adds /api/health.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A Coolify persistent storage entry was overriding /app/server.py with an
unresolved ${FILE:} placeholder, causing SyntaxError on startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extensions like LegalAssistance that are in the registry but haven't
been published yet (latest_version is null) now show "אין גרסה"
instead of a deploy button. Batch deploy also skips them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
/api/extensions and customer extension list now only return
is_active extensions, preventing deleted extensions from showing
in the dashboard and causing deploy errors with null versions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every time the server starts (including after deploy), it syncs
the extension registry from Gitea before serving requests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MODULES was a static dict that got out of sync with Gitea repos —
LegalAssistance was missing, 5 deleted extensions were still listed.
Now MODULES loads from DB on startup with static fallback.
New POST /sync-registry endpoint scans espocrm-extensions org on Gitea,
adds new extensions, deactivates removed ones, and reloads MODULES.
INSTALL_ORDER is now computed dynamically via topological sort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The platform stores API keys as base64-encoded Espo-Authorization,
not raw API keys. Send both headers so opcache-clear.php can validate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add multi-tier opcache clearing after extension install:
- Tier 1: HTTP call to opcache-clear.php endpoint
- Tier 2: SSH + docker exec apachectl graceful
- Tier 3: Log warning
Add ssh_host, ssh_user, ssh_key, docker_container fields
to customers table for SSH fallback.
Fixes production issue where new PHP files weren't loaded
after extension upgrade due to stale opcache.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was showing all historical deploys causing noise. Now uses DISTINCT ON
to get only the most recent deploy for each extension+customer pair.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New /health-check endpoint verifies:
- Gitea release integrity (manifest version matches tag)
- Duplicate ZIP assets in releases
- EspoCRM API reachability per customer
- Extension load status on each instance
- Stale deployments (logged ok but version not updated)
New n8n workflow runs at 07:00 & 19:00, combines health check
with drift sync, and posts combined report to Mattermost.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- When re-publishing, delete old ZIP assets before uploading new one
- Take last ZIP asset when duplicates exist in deploy flow
- Handle empty response body from DELETE API calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ensures every sync call first fills in any missing extension
assignments, so new extensions are automatically available to
all customers without manual intervention.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auth fields: use grid-cols-2 with dir=ltr for proper username/password
layout, better spacing and visual hierarchy with key icon
- Header: increase opacity to 0.95 with subtle shadow to prevent
content showing through when scrolling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of manually entering base64, users can now type username and
password which auto-generates the Espo-Authorization header value.
Empty credentials on edit won't overwrite existing API key.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Backend: sync_customer_extensions now sets installed_version=NULL when
extension is missing_on_server (previously only detected drift without
updating DB)
- Frontend: refresh button now calls /sync endpoint before reloading,
ensuring the dashboard reflects actual server state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds extension sync that queries each customer's EspoCRM to compare
actually-installed extensions with what the platform DB tracks.
Updates platform DB on version mismatches and reports drifts to Mattermost.
n8n workflow ID: NXxBHm3qhWL3yHza (daily at 07:00)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sort extensions by install order (HebrewLanguage first, then deps)
- Grey out extensions with missing dependencies (show lock icon + "נדרש: X")
- Checkbox selection for batch install with progress indicator
- "בחר הכל" button to select all installable extensions
- Server returns dependencies + install_order in customer API
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>