all support, core,cli,gui,essentiials
This commit is contained in:
parent
693c3404ad
commit
047c5b2ed0
139 changed files with 850 additions and 647 deletions
72
README.md
72
README.md
|
|
@ -1,10 +1,29 @@
|
|||
# hydra-veil — usage
|
||||
|
||||
## 1. Create billing code / Crear billing code
|
||||
|
||||
```python
|
||||
python3 -c "
|
||||
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)
|
||||
"
|
||||
```
|
||||
|
||||
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="
|
||||
\$sub = \App\Models\Subscription::orderBy('id', 'desc')->first();
|
||||
\$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();
|
||||
echo 'OK' . PHP_EOL;
|
||||
"
|
||||
conectar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Connect / Conectar
|
||||
|
||||
### VLESS
|
||||
|
||||
```python
|
||||
python3 -c "
|
||||
from core.services.WebServiceApiService import WebServiceApiService
|
||||
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('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)
|
||||
|
||||
controller = VlessController(1080)
|
||||
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)
|
||||
"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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
70
core/Constants.py
Normal 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'
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import socket
|
||||
import time
|
||||
from pathlib import Path
|
||||
from core.Constants import Constants
|
||||
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,
|
||||
server_host: str, socks5_port: int) -> dict:
|
||||
server_ip = _resolve(server_host)
|
||||
|
||||
return {
|
||||
"dns": {
|
||||
"servers": [{"tag": "local", "type": "udp", "server": "9.9.9.9"}],
|
||||
|
|
@ -55,6 +57,8 @@ def build_hysteria_config(username: str, password: str,
|
|||
"rules": [
|
||||
{"protocol": "dns", "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_is_private": True, "outbound": "direct"},
|
||||
{"ip_version": 6, "outbound": "block"},
|
||||
|
|
@ -85,7 +89,11 @@ def enable_hysteria(username: str, password: str, server_host: str,
|
|||
if observer:
|
||||
observer.notify("error", "sing-box not active after start")
|
||||
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:
|
||||
observer.notify("connected", {
|
||||
"real_ip": real_ip,
|
||||
|
|
@ -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.singbox import SingboxRunner
|
||||
import socket
|
||||
import time
|
||||
|
||||
|
||||
def parse_vless_link(link: str) -> dict:
|
||||
|
|
@ -36,7 +37,13 @@ def build_vless_config(vless: dict, socks5_port: int) -> dict:
|
|||
|
||||
return {
|
||||
"dns": {
|
||||
"servers": [{"tag": "local", "type": "udp", "server": "9.9.9.9"}],
|
||||
"servers": [
|
||||
{
|
||||
"tag": "local",
|
||||
"type": "udp",
|
||||
"server": "9.9.9.9",
|
||||
},
|
||||
],
|
||||
"final": "local",
|
||||
"strategy": "ipv4_only",
|
||||
},
|
||||
|
|
@ -82,6 +89,7 @@ def build_vless_config(vless: dict, socks5_port: int) -> dict:
|
|||
"rules": [
|
||||
{"protocol": "dns", "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_is_private": True, "outbound": "direct"},
|
||||
{"ip_version": 6, "outbound": "block"},
|
||||
|
|
@ -113,6 +121,10 @@ def enable_vless(vless_link: str, username: str,
|
|||
if observer:
|
||||
observer.notify("error", "sing-box not active after start")
|
||||
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)
|
||||
if observer:
|
||||
observer.notify("connected", {
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue