all support, core,cli,gui,essentiials

This commit is contained in:
Zenaku 2026-06-02 07:19:17 -05:00
parent 693c3404ad
commit 047c5b2ed0
139 changed files with 850 additions and 647 deletions

View file

@ -1,10 +1,29 @@
# hydra-veil — usage
## 1. Create billing code / Crear billing code
```python
python3 -c " python3 -c "
from core.services.WebServiceApiService import WebServiceApiService from core.services.WebServiceApiService import WebServiceApiService
subscription = WebServiceApiService.post_subscription(2, operator_id=3) subscription = WebServiceApiService.post_subscription(2, operator_id=<OPERATOR_ID>)
print('billing code:', subscription.billing_code) print('billing code:', subscription.billing_code)
" "
```
parchear > Copy the printed `billing_code` — you'll need it in the connect step.
> Copiá el `billing_code` impreso, lo vas a necesitar en el paso de conexión.
---
consultar estado de invoive
python3 -c "
from core.services.WebServiceApiService import WebServiceApiService
invoice = WebServiceApiService.get_invoice('BILLING_CODE_HERE')
print('status:', invoice.status)
"
## 2. Patch subscription / Parchear suscripción
```bash
sudo docker compose exec laravel.test php artisan tinker --execute=" sudo docker compose exec laravel.test php artisan tinker --execute="
\$sub = \App\Models\Subscription::orderBy('id', 'desc')->first(); \$sub = \App\Models\Subscription::orderBy('id', 'desc')->first();
\$sub->expires_at = '2027-12-31 23:59:59'; \$sub->expires_at = '2027-12-31 23:59:59';
@ -13,7 +32,15 @@ sudo docker compose exec laravel.test php artisan tinker --execute="
\$sub->save(); \$sub->save();
echo 'OK' . PHP_EOL; echo 'OK' . PHP_EOL;
" "
conectar ```
---
## 3. Connect / Conectar
### VLESS
```python
python3 -c " python3 -c "
from core.services.WebServiceApiService import WebServiceApiService from core.services.WebServiceApiService import WebServiceApiService
from core.controllers.encrypted_proxy.VlessController import VlessController from core.controllers.encrypted_proxy.VlessController import VlessController
@ -24,11 +51,48 @@ observer.subscribe('connected', lambda e: print('connected:', e.subject))
observer.subscribe('error', lambda e: print('error:', e.subject)) observer.subscribe('error', lambda e: print('error:', e.subject))
observer.subscribe('disconnected', lambda e: print('disconnected:', e.subject)) observer.subscribe('disconnected', lambda e: print('disconnected:', e.subject))
session = WebServiceApiService.post_operator_proxy('617N-LPKB-MVPT-YRQM', 3, 'vless') session = WebServiceApiService.post_operator_proxy('<BILLING_CODE>', <OPERATOR_ID>, 'vless')
print('session:', session) print('session:', session)
controller = VlessController(1080) controller = VlessController(1080)
controller.enable(session.links[0], session.username, observer) controller.enable(session.links[0], session.username, observer)
" "
```
### Hysteria2
```python
python3 -c "
from core.services.WebServiceApiService import WebServiceApiService
from core.controllers.encrypted_proxy.HysteriaController import HysteriaController
from core.observers.EncryptedProxyObserver import EncryptedProxyObserver
observer = EncryptedProxyObserver()
observer.subscribe('connected', lambda e: print('connected:', e.subject))
observer.subscribe('error', lambda e: print('error:', e.subject))
observer.subscribe('disconnected', lambda e: print('disconnected:', e.subject))
session = WebServiceApiService.post_operator_proxy('<BILLING_CODE>', <OPERATOR_ID>, 'hysteria2')
print('session:', session)
controller = HysteriaController(1080)
controller.enable(session.username, session.password, session.operator_hysteria2_host, observer)
"
```
---
## 4. Disconnect / Desconectar
```python
python3 -c "
from core.controllers.encrypted_proxy.HysteriaController import HysteriaController
from core.observers.EncryptedProxyObserver import EncryptedProxyObserver
observer = EncryptedProxyObserver()
observer.subscribe('disconnected', lambda e: print('disconnected'))
controller = HysteriaController(1080)
controller.disable(observer)
"
```

View file

@ -1,50 +0,0 @@
from dataclasses import dataclass
from typing import Final
from dotenv import load_dotenv
import os
load_dotenv(os.path.join(os.path.dirname(__file__), '../.env'))
_env = os.environ.get('APP_ENV', 'production')
_sp_api_base_url = (
os.environ.get('SP_API_BASE_URL_LOCAL', 'http://simplifiedprivacy.test/api/v1')
if _env == 'local'
else os.environ.get('SP_API_BASE_URL_PRODUCTION', 'https://api.hydraveil.net/api/v1')
)
@dataclass(frozen=True)
class Constants:
TICKET_API_BASE_URL: Final[str] = os.environ.get(
"TICKET_API_BASE_URL", "https://ticket.hydraveil.net"
)
SP_API_BASE_URL: Final[str] = _sp_api_base_url
PING_URL: Final[str] = os.environ.get('PING_URL', f'{_sp_api_base_url}/health')
CONNECTION_RETRY_INTERVAL: Final[int] = int(os.environ.get('CONNECTION_RETRY_INTERVAL', '5'))
MAX_CONNECTION_ATTEMPTS: Final[int] = int(os.environ.get('MAX_CONNECTION_ATTEMPTS', '2'))
HV_CLIENT_PATH: Final[str] = os.environ.get('HV_CLIENT_PATH')
HV_CLIENT_VERSION_NUMBER: Final[str] = os.environ.get('HV_CLIENT_VERSION_NUMBER')
HOME: Final[str] = os.path.expanduser('~')
SYSTEM_CONFIG_PATH: Final[str] = '/etc'
INSTALL_DIR: Final[str] = os.environ.get('INSTALL_DIR', os.path.expanduser('~'))
CACHE_HOME: Final[str] = os.environ.get('XDG_CACHE_HOME', f'{INSTALL_DIR}/.cache')
CONFIG_HOME: Final[str] = os.environ.get('XDG_CONFIG_HOME', f'{INSTALL_DIR}/.config')
DATA_HOME: Final[str] = os.environ.get('XDG_DATA_HOME', f'{INSTALL_DIR}/data')
STATE_HOME: Final[str] = os.environ.get('XDG_STATE_HOME', f'{INSTALL_DIR}/.state')
HV_SYSTEM_CONFIG_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/hydra-veil'
HV_CACHE_HOME: Final[str] = f'{CACHE_HOME}/hydra-veil'
HV_CONFIG_HOME: Final[str] = f'{CONFIG_HOME}/hydra-veil'
HV_DATA_HOME: Final[str] = f'{DATA_HOME}/hydra-veil'
HV_STATE_HOME: Final[str] = f'{STATE_HOME}/hydra-veil'
HV_SYSTEM_PROFILE_CONFIG_PATH: Final[str] = f'{HV_SYSTEM_CONFIG_PATH}/profiles'
HV_PROFILE_CONFIG_HOME: Final[str] = f'{HV_CONFIG_HOME}/profiles'
HV_PROFILE_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/profiles'
HV_TICKETING_CONFIG_HOME: Final[str] = f"{HV_CONFIG_HOME}/ticketing"
HV_TICKETING_DATA_HOME: Final[str] = f"{HV_DATA_HOME}/ticket_data"
HV_APPLICATION_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/applications'
HV_INCIDENT_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/incidents'
HV_RUNTIME_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/runtime'
HV_STORAGE_DATABASE_PATH: Final[str] = f'{HV_DATA_HOME}/storage.db'
HV_CAPABILITY_POLICY_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/apparmor.d/hydra-veil'
HV_PRIVILEGE_POLICY_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/sudoers.d/hydra-veil'
HV_SESSION_STATE_HOME: Final[str] = f'{HV_STATE_HOME}/sessions'
HV_TOR_STATE_HOME: Final[str] = f'{HV_STATE_HOME}/tor'

View file

@ -1,44 +0,0 @@
import socket
import subprocess
import time
def resolve(host: str) -> str | None:
try:
return socket.gethostbyname(host)
except Exception:
return None
def get_real_ip(timeout: int = 10) -> str:
try:
r = subprocess.run(
["curl", "-s", "--max-time", str(timeout), "https://ifconfig.me/ip"],
capture_output=True, timeout=timeout + 2,
)
return r.stdout.decode().strip() or "unknown (empty)"
except Exception as e:
return f"unknown ({e})"
def get_proxied_ip(socks5_port: int, timeout: int = 10) -> str:
try:
r = subprocess.run(
["curl", "-s", "--max-time", str(timeout),
"-x", f"socks5h://127.0.0.1:{socks5_port}",
"https://ifconfig.me/ip"],
capture_output=True, timeout=timeout + 2,
)
return r.stdout.decode().strip() or "unknown (empty)"
except Exception as e:
return f"unknown ({e})"
def verify_ip(socks5_port: int, retries: int = 3, delay: float = 2.0) -> str:
for attempt in range(1, retries + 1):
ip = get_proxied_ip(socks5_port)
if not ip.startswith("unknown"):
return ip
if attempt < retries:
time.sleep(delay)
return "unknown"

70
core/Constants.py Normal file
View file

@ -0,0 +1,70 @@
from dataclasses import dataclass
from typing import Final
from dotenv import load_dotenv
import os
load_dotenv(os.path.join(os.path.dirname(__file__), '../.env'))
_env = os.environ.get('APP_ENV', 'production')
_sp_api_base_url = (
os.environ.get('SP_API_BASE_URL_LOCAL', 'http://simplifiedprivacy.test/api/v1')
if _env == 'local'
else os.environ.get('SP_API_BASE_URL_PRODUCTION', 'https://api.hydraveil.net/api/v1')
)
@dataclass(frozen=True)
class Constants:
TICKET_API_BASE_URL: Final[str] = os.environ.get(
"TICKET_API_BASE_URL", "https://ticket.hydraveil.net"
)
SP_API_BASE_URL: Final[str] = _sp_api_base_url
PING_URL: Final[str] = os.environ.get('PING_URL', f'{_sp_api_base_url}/health')
CONNECTION_RETRY_INTERVAL: Final[int] = int(os.environ.get('CONNECTION_RETRY_INTERVAL', '5'))
MAX_CONNECTION_ATTEMPTS: Final[int] = int(os.environ.get('MAX_CONNECTION_ATTEMPTS', '2'))
HV_CLIENT_PATH: Final[str] = os.environ.get('HV_CLIENT_PATH')
HV_CLIENT_VERSION_NUMBER: Final[str] = os.environ.get('HV_CLIENT_VERSION_NUMBER')
# ── Paths base ────────────────────────────────────────────────────────────
HOME: Final[str] = os.path.expanduser('~')
SYSTEM_CONFIG_PATH: Final[str] = '/etc'
INSTALL_DIR: Final[str] = os.environ.get('INSTALL_DIR', os.path.expanduser('~'))
# ── XDG ───────────────────────────────────────────────────────────────────
CACHE_HOME: Final[str] = os.environ.get('XDG_CACHE_HOME', f'{INSTALL_DIR}/.cache')
CONFIG_HOME: Final[str] = os.environ.get('XDG_CONFIG_HOME', f'{INSTALL_DIR}/.config')
DATA_HOME: Final[str] = os.environ.get('XDG_DATA_HOME', f'{INSTALL_DIR}/data')
STATE_HOME: Final[str] = os.environ.get('XDG_STATE_HOME', f'{INSTALL_DIR}/.state')
# ── hydra-veil dirs ───────────────────────────────────────────────────────
HV_SYSTEM_CONFIG_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/hydra-veil'
HV_CACHE_HOME: Final[str] = f'{CACHE_HOME}/hydra-veil'
HV_CONFIG_HOME: Final[str] = f'{CONFIG_HOME}/hydra-veil'
HV_DATA_HOME: Final[str] = f'{DATA_HOME}/hydra-veil'
HV_STATE_HOME: Final[str] = f'{STATE_HOME}/hydra-veil'
HV_SYSTEM_PROFILE_CONFIG_PATH: Final[str] = f'{HV_SYSTEM_CONFIG_PATH}/profiles'
HV_PROFILE_CONFIG_HOME: Final[str] = f'{HV_CONFIG_HOME}/profiles'
HV_PROFILE_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/profiles'
HV_TICKETING_CONFIG_HOME: Final[str] = f'{HV_CONFIG_HOME}/ticketing'
HV_TICKETING_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/ticket_data'
HV_APPLICATION_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/applications'
HV_INCIDENT_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/incidents'
HV_RUNTIME_DATA_HOME: Final[str] = f'{HV_DATA_HOME}/runtime'
HV_STORAGE_DATABASE_PATH: Final[str] = f'{HV_DATA_HOME}/storage.db'
HV_CAPABILITY_POLICY_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/apparmor.d/hydra-veil'
HV_PRIVILEGE_POLICY_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/sudoers.d/hydra-veil'
HV_SESSION_STATE_HOME: Final[str] = f'{HV_STATE_HOME}/sessions'
HV_TOR_STATE_HOME: Final[str] = f'{HV_STATE_HOME}/tor'
# ── sing-box ──────────────────────────────────────────────────────────────
SINGBOX_WRAPPER: Final[str] = os.environ.get(
'SINGBOX_WRAPPER', '/usr/local/bin/hydraveil-singbox'
)
SINGBOX_BIN: Final[str] = os.environ.get(
'SINGBOX_BIN', '/usr/bin/sing-box'
)
SINGBOX_CONFIG_DIR: Final[str] = f'{HV_DATA_HOME}/configs'
SINGBOX_PID_FILE: Final[str] = f'{HV_RUNTIME_DATA_HOME}/singbox.pid'
SINGBOX_LOG_FILE: Final[str] = f'{HV_RUNTIME_DATA_HOME}/singbox.log'

View file

@ -1,4 +1,5 @@
import socket import socket
import time
from pathlib import Path from pathlib import Path
from core.Constants import Constants from core.Constants import Constants
from core.utils.encrypted_proxy.net import get_real_ip, verify_ip from core.utils.encrypted_proxy.net import get_real_ip, verify_ip
@ -12,6 +13,7 @@ def _resolve(host: str) -> str:
def build_hysteria_config(username: str, password: str, def build_hysteria_config(username: str, password: str,
server_host: str, socks5_port: int) -> dict: server_host: str, socks5_port: int) -> dict:
server_ip = _resolve(server_host) server_ip = _resolve(server_host)
return { return {
"dns": { "dns": {
"servers": [{"tag": "local", "type": "udp", "server": "9.9.9.9"}], "servers": [{"tag": "local", "type": "udp", "server": "9.9.9.9"}],
@ -55,6 +57,8 @@ def build_hysteria_config(username: str, password: str,
"rules": [ "rules": [
{"protocol": "dns", "action": "hijack-dns"}, {"protocol": "dns", "action": "hijack-dns"},
{"ip_cidr": ["172.19.0.0/30"], "action": "hijack-dns"}, {"ip_cidr": ["172.19.0.0/30"], "action": "hijack-dns"},
# DNS y servidor salen directo — nunca por el TUN
{"ip_cidr": ["9.9.9.9/32"], "outbound": "direct"},
{"ip_cidr": [f"{server_ip}/32"], "outbound": "direct"}, {"ip_cidr": [f"{server_ip}/32"], "outbound": "direct"},
{"ip_is_private": True, "outbound": "direct"}, {"ip_is_private": True, "outbound": "direct"},
{"ip_version": 6, "outbound": "block"}, {"ip_version": 6, "outbound": "block"},
@ -85,7 +89,11 @@ def enable_hysteria(username: str, password: str, server_host: str,
if observer: if observer:
observer.notify("error", "sing-box not active after start") observer.notify("error", "sing-box not active after start")
return False return False
proxy_ip = verify_ip(socks5_port)
# Esperar que el proxy esté listo para rutear tráfico
time.sleep(3)
proxy_ip = verify_ip(socks5_port, retries=5, delay=3.0)
if observer: if observer:
observer.notify("connected", { observer.notify("connected", {
"real_ip": real_ip, "real_ip": real_ip,

View file

@ -4,6 +4,7 @@ from core.Constants import Constants
from core.utils.encrypted_proxy.net import get_real_ip, verify_ip from core.utils.encrypted_proxy.net import get_real_ip, verify_ip
from core.utils.encrypted_proxy.singbox import SingboxRunner from core.utils.encrypted_proxy.singbox import SingboxRunner
import socket import socket
import time
def parse_vless_link(link: str) -> dict: def parse_vless_link(link: str) -> dict:
@ -36,7 +37,13 @@ def build_vless_config(vless: dict, socks5_port: int) -> dict:
return { return {
"dns": { "dns": {
"servers": [{"tag": "local", "type": "udp", "server": "9.9.9.9"}], "servers": [
{
"tag": "local",
"type": "udp",
"server": "9.9.9.9",
},
],
"final": "local", "final": "local",
"strategy": "ipv4_only", "strategy": "ipv4_only",
}, },
@ -82,6 +89,7 @@ def build_vless_config(vless: dict, socks5_port: int) -> dict:
"rules": [ "rules": [
{"protocol": "dns", "action": "hijack-dns"}, {"protocol": "dns", "action": "hijack-dns"},
{"ip_cidr": ["172.19.0.0/30"], "action": "hijack-dns"}, {"ip_cidr": ["172.19.0.0/30"], "action": "hijack-dns"},
{"ip_cidr": ["9.9.9.9/32"], "outbound": "direct"},
{"ip_cidr": [f"{server_ip}/32"], "outbound": "direct"}, {"ip_cidr": [f"{server_ip}/32"], "outbound": "direct"},
{"ip_is_private": True, "outbound": "direct"}, {"ip_is_private": True, "outbound": "direct"},
{"ip_version": 6, "outbound": "block"}, {"ip_version": 6, "outbound": "block"},
@ -113,6 +121,10 @@ def enable_vless(vless_link: str, username: str,
if observer: if observer:
observer.notify("error", "sing-box not active after start") observer.notify("error", "sing-box not active after start")
return False return False
# Esperar que el proxy esté listo para rutear tráfico
time.sleep(3)
proxy_ip = verify_ip(socks5_port, retries=5, delay=3.0) proxy_ip = verify_ip(socks5_port, retries=5, delay=3.0)
if observer: if observer:
observer.notify("connected", { observer.notify("connected", {

Some files were not shown because too many files have changed in this diff Show more