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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ META_APP_SECRET=
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=

# ── License — headless auto-activation ───────────────
# Set this to the email used in your first manual license registration.
# On startup, EvoNexus calls /v1/register/auto silently and skips the manual
# setup screen. Falls back to manual setup if the email isn't registered yet.
# Leave empty (or unset) to keep the default behavior.
# EVOLUTION_OPERATOR_EMAIL=operator@example.com

# ── Evolution API ────────────────────────────────────
# Your Evolution API instance URL and global API key
EVOLUTION_API_URL=
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Harassment, discrimination, or abusive behavior will not be tolerated.

### Reporting Bugs

1. Check existing [issues](https://github.com/EvolutionAPI/evo-nexus/issues)
1. Check existing [issues](https://github.com/evolution-foundation/evo-nexus/issues)
to avoid duplicates
2. Open a new issue with:
- Clear, descriptive title
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</p>

<p align="center">
<a href="https://github.com/EvolutionAPI/evo-nexus/releases/latest"><img src="https://img.shields.io/github/v/release/EvolutionAPI/evo-nexus?include_prereleases&label=version&color=00ffa7" alt="Latest version" /></a>
<a href="https://github.com/evolution-foundation/evo-nexus/releases/latest"><img src="https://img.shields.io/github/v/release/evolution-foundation/evo-nexus?include_prereleases&label=version&color=00ffa7" alt="Latest version" /></a>
<a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0" /></a>
<a href="https://docs.evolutionfoundation.com.br"><img src="https://img.shields.io/badge/Docs-evolutionfoundation.com.br-00ffa7" alt="Documentation" /></a>
<a href="https://evolutionfoundation.com.br/community"><img src="https://img.shields.io/badge/Community-Join%20us-white" alt="Community" /></a>
Expand Down Expand Up @@ -46,7 +46,7 @@ It turns a single CLI installation into a team of **38 specialized agents** orga

## Part of the Evolution Foundation ecosystem

EvoNexus is one of the projects maintained by Evolution Foundation. It is the operating layer that orchestrates the Foundation's own work — including the development of [Evo CRM Community](https://github.com/EvolutionAPI/evo-crm-community), [Evolution API](https://github.com/EvolutionAPI/evolution-api) and [Evolution Go](https://github.com/EvolutionAPI/evolution-go).
EvoNexus is one of the projects maintained by Evolution Foundation. It is the operating layer that orchestrates the Foundation's own work — including the development of [Evo CRM Community](https://github.com/evolution-foundation/evo-crm-community), [Evolution API](https://github.com/evolution-foundation/evolution-api) and [Evolution Go](https://github.com/evolution-foundation/evolution-go).

### Why EvoNexus?

Expand Down Expand Up @@ -101,7 +101,7 @@ EvoNexus is one of the projects maintained by Evolution Foundation. It is the op
### Method 1 — Docker (no setup, runs anywhere)

```bash
curl -O https://raw.githubusercontent.com/EvolutionAPI/evo-nexus/main/docker-compose.hub.yml
curl -O https://raw.githubusercontent.com/evolution-foundation/evo-nexus/main/docker-compose.hub.yml
docker compose -f docker-compose.hub.yml up -d
open http://localhost:8080
```
Expand All @@ -117,7 +117,7 @@ npx @evoapi/evo-nexus
### Method 3 — Manual clone (developers / contributors)

```bash
git clone --depth 1 https://github.com/EvolutionAPI/evo-nexus.git
git clone --depth 1 https://github.com/evolution-foundation/evo-nexus.git
cd evo-nexus

# Interactive setup wizard
Expand Down
2 changes: 1 addition & 1 deletion dashboard/backend/brain_repo/job_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def _load_config_snapshot(flask_app, user_id: int) -> dict | None:
# Pipeline steps — each checks cancel before doing work.
# ────────────────────────────────────────────────────────────────────────

_WATCH_PATHS = ["memory", "workspace", "customizations", "config-safe"]
_WATCH_PATHS = ["memory", "workspace", "customizations", "config-safe", ".claude/agent-memory"]

# Relative paths (POSIX) that are NEVER mirrored into the brain repo.
# `workspace/projects/` is where user-cloned git repos live (per the project's
Expand Down
1 change: 1 addition & 0 deletions dashboard/backend/brain_repo/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"workspace",
"customizations",
"config-safe",
".claude/agent-memory",
]

EXCLUDE_PATHS = [
Expand Down
81 changes: 76 additions & 5 deletions dashboard/backend/licensing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

Protocol:
POST /v1/register/direct — register with email/name, receive api_key
POST /v1/register/auto — headless register by email (must exist server-side)
POST /v1/activate — validate existing api_key on startup
GET /api/geo — geo-lookup from client IP
"""

import hashlib
import hmac as hmac_mod
import os
import socket
import uuid
import logging
Expand Down Expand Up @@ -155,6 +157,24 @@ def direct_register(email: str, name: str, instance_id: str,
return _post("/v1/register/direct", payload)


# ── Auto Registration (email-only, headless) ──

def auto_register(email: str, instance_id: str) -> dict:
"""Headless registration using only the operator email.

The customer must already exist on the licensing server (one prior manual
registration). Used by the EVOLUTION_OPERATOR_EMAIL env-var flow.

Returns {api_key, customer_id, tier, status}.
"""
return _post("/v1/register/auto", {
"email": email,
"tier": TIER,
"instance_id": instance_id,
"version": VERSION,
})


# ── Activation (startup with existing api_key) ──

def activate(instance_id: str, api_key: str) -> bool:
Expand Down Expand Up @@ -260,8 +280,54 @@ def initialize_runtime():

# ── Auto-register for existing installs ──────

def try_auto_register_from_env(instance_id: str) -> bool:
"""Headless activation via EVOLUTION_OPERATOR_EMAIL env var.

Requires the email to already exist on the licensing server (one prior
manual registration). Returns True on success.

Failures are silent — caller falls back to the existing admin-based or
manual setup flow.
"""
email = os.environ.get("EVOLUTION_OPERATOR_EMAIL", "").strip()
if not email:
return False

try:
result = auto_register(email=email, instance_id=instance_id)
except requests.HTTPError as e:
status = e.response.status_code if e.response is not None else "?"
if status == 404:
logger.info("Auto-activation skipped — email not registered yet (first time?).")
else:
logger.warning(f"Auto-activation rejected ({status}): falling back to manual flow.")
return False
except Exception as e:
logger.warning(f"Auto-activation skipped — {e}")
return False

api_key = result.get("api_key")
if not api_key:
logger.warning("Auto-activation response missing api_key")
return False

set_runtime_config("api_key", api_key)
set_runtime_config("tier", result.get("tier", TIER))
if result.get("customer_id"):
set_runtime_config("customer_id", str(result["customer_id"]))
set_runtime_config("version", VERSION)
set_runtime_config("registered_at", datetime.now(timezone.utc).isoformat())

ctx = get_context()
ctx.api_key = api_key
ctx.instance_id = instance_id
logger.info("License activated automatically via EVOLUTION_OPERATOR_EMAIL")
return True


def auto_register_if_needed():
"""If users exist but no license, register retroactively."""
"""If no license yet, try EVOLUTION_OPERATOR_EMAIL first, then fall back to
the admin-based retroactive flow."""
try:
instance_id = get_runtime_config("instance_id")
api_key = get_runtime_config("api_key")
Expand All @@ -270,6 +336,15 @@ def auto_register_if_needed():
initialize_runtime()
return

if not instance_id:
instance_id = generate_instance_id()
set_runtime_config("instance_id", instance_id)

# First-class path: silent activation from env var.
if try_auto_register_from_env(instance_id):
return

# Fallback: if there's an admin user already, register retroactively.
from models import User
if User.query.count() == 0:
return
Expand All @@ -278,10 +353,6 @@ def auto_register_if_needed():
if not admin or not admin.email:
return

if not instance_id:
instance_id = generate_instance_id()
set_runtime_config("instance_id", instance_id)

setup_perform(
email=admin.email or "",
name=admin.display_name or admin.username,
Expand Down