44
55Protocol:
66 POST /v1/register/direct — register with email/name, receive api_key
7+ POST /v1/register/auto — headless register by email (must exist server-side)
78 POST /v1/activate — validate existing api_key on startup
89 GET /api/geo — geo-lookup from client IP
910"""
1011
1112import hashlib
1213import hmac as hmac_mod
14+ import os
1315import socket
1416import uuid
1517import logging
@@ -155,6 +157,24 @@ def direct_register(email: str, name: str, instance_id: str,
155157 return _post ("/v1/register/direct" , payload )
156158
157159
160+ # ── Auto Registration (email-only, headless) ──
161+
162+ def auto_register (email : str , instance_id : str ) -> dict :
163+ """Headless registration using only the operator email.
164+
165+ The customer must already exist on the licensing server (one prior manual
166+ registration). Used by the EVOLUTION_OPERATOR_EMAIL env-var flow.
167+
168+ Returns {api_key, customer_id, tier, status}.
169+ """
170+ return _post ("/v1/register/auto" , {
171+ "email" : email ,
172+ "tier" : TIER ,
173+ "instance_id" : instance_id ,
174+ "version" : VERSION ,
175+ })
176+
177+
158178# ── Activation (startup with existing api_key) ──
159179
160180def activate (instance_id : str , api_key : str ) -> bool :
@@ -260,8 +280,54 @@ def initialize_runtime():
260280
261281# ── Auto-register for existing installs ──────
262282
283+ def try_auto_register_from_env (instance_id : str ) -> bool :
284+ """Headless activation via EVOLUTION_OPERATOR_EMAIL env var.
285+
286+ Requires the email to already exist on the licensing server (one prior
287+ manual registration). Returns True on success.
288+
289+ Failures are silent — caller falls back to the existing admin-based or
290+ manual setup flow.
291+ """
292+ email = os .environ .get ("EVOLUTION_OPERATOR_EMAIL" , "" ).strip ()
293+ if not email :
294+ return False
295+
296+ try :
297+ result = auto_register (email = email , instance_id = instance_id )
298+ except requests .HTTPError as e :
299+ status = e .response .status_code if e .response is not None else "?"
300+ if status == 404 :
301+ logger .info ("Auto-activation skipped — email not registered yet (first time?)." )
302+ else :
303+ logger .warning (f"Auto-activation rejected ({ status } ): falling back to manual flow." )
304+ return False
305+ except Exception as e :
306+ logger .warning (f"Auto-activation skipped — { e } " )
307+ return False
308+
309+ api_key = result .get ("api_key" )
310+ if not api_key :
311+ logger .warning ("Auto-activation response missing api_key" )
312+ return False
313+
314+ set_runtime_config ("api_key" , api_key )
315+ set_runtime_config ("tier" , result .get ("tier" , TIER ))
316+ if result .get ("customer_id" ):
317+ set_runtime_config ("customer_id" , str (result ["customer_id" ]))
318+ set_runtime_config ("version" , VERSION )
319+ set_runtime_config ("registered_at" , datetime .now (timezone .utc ).isoformat ())
320+
321+ ctx = get_context ()
322+ ctx .api_key = api_key
323+ ctx .instance_id = instance_id
324+ logger .info ("License activated automatically via EVOLUTION_OPERATOR_EMAIL" )
325+ return True
326+
327+
263328def auto_register_if_needed ():
264- """If users exist but no license, register retroactively."""
329+ """If no license yet, try EVOLUTION_OPERATOR_EMAIL first, then fall back to
330+ the admin-based retroactive flow."""
265331 try :
266332 instance_id = get_runtime_config ("instance_id" )
267333 api_key = get_runtime_config ("api_key" )
@@ -270,6 +336,15 @@ def auto_register_if_needed():
270336 initialize_runtime ()
271337 return
272338
339+ if not instance_id :
340+ instance_id = generate_instance_id ()
341+ set_runtime_config ("instance_id" , instance_id )
342+
343+ # First-class path: silent activation from env var.
344+ if try_auto_register_from_env (instance_id ):
345+ return
346+
347+ # Fallback: if there's an admin user already, register retroactively.
273348 from models import User
274349 if User .query .count () == 0 :
275350 return
@@ -278,10 +353,6 @@ def auto_register_if_needed():
278353 if not admin or not admin .email :
279354 return
280355
281- if not instance_id :
282- instance_id = generate_instance_id ()
283- set_runtime_config ("instance_id" , instance_id )
284-
285356 setup_perform (
286357 email = admin .email or "" ,
287358 name = admin .display_name or admin .username ,
0 commit comments