Core configuration synchronized with the CLI
This commit is contained in:
parent
ffc6100660
commit
4605f1b9cc
2 changed files with 293 additions and 28 deletions
290
README.md
290
README.md
|
|
@ -1,34 +1,282 @@
|
|||
# sp-hydra-veil-cli
|
||||
# hydra-veil CLI
|
||||
|
||||
The `sp-hydra-veil-cli` command line interface is the reference implementation of the `sp-hydra-veil-core` library.
|
||||
> [English](#english) · [Español](#español)
|
||||
|
||||
## Build Instructions
|
||||
---
|
||||
|
||||
### Presumptions
|
||||
### What is this?
|
||||
|
||||
* Your system is configured to use the Simplified Privacy package registry [1].
|
||||
A command-line tool to manage VPN profiles and encrypted proxy connections. It supports two connection types:
|
||||
|
||||
### Prerequisites
|
||||
- **WireGuard** — system-level VPN tied to a specific location
|
||||
- **Operator Proxy** — encrypted proxy (vless or hysteria2) tied to an operator
|
||||
|
||||
* `build`
|
||||
* `twine`
|
||||
---
|
||||
|
||||
To install them, activate your `venv`, if necessary, and run:
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install build twine
|
||||
```
|
||||
|
||||
### Build Steps
|
||||
|
||||
* Activate your `venv`, if necessary, and run:
|
||||
|
||||
```bash
|
||||
rm -rf ./dist/*
|
||||
python3 -m build
|
||||
python3 -m twine upload --repository forgejo ./dist/*
|
||||
pip install -e .
|
||||
python -m cli --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[1] https://forgejo.org/docs/v14.0/user/packages/pypi/#configuring-the-package-registry
|
||||
### Commands overview
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `profile` | Manage connection profiles |
|
||||
| `application` | Manage supported applications |
|
||||
| `operator` | List available operators |
|
||||
| `policy` | Manage system policies |
|
||||
| `get` / `set` | Read / write configuration |
|
||||
| `sync` | Sync data from the server |
|
||||
| `update` | Update the client |
|
||||
|
||||
---
|
||||
|
||||
### Creating a profile
|
||||
|
||||
#### WireGuard (system-level VPN)
|
||||
|
||||
```bash
|
||||
python -m cli profile create system -i 2 -n "my-vpn" -l de:be
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
|---|---|
|
||||
| `-i` | Profile ID (unique integer) |
|
||||
| `-n` | Profile name (any label) |
|
||||
| `-l` | Location in `country:city` format |
|
||||
|
||||
#### Operator Proxy
|
||||
|
||||
```bash
|
||||
python -m cli profile create operator -i 8 -n "zenaku-vless" -o 3 -pr vless
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
|---|---|
|
||||
| `-i` | Profile ID (unique integer) |
|
||||
| `-n` | Profile name (any label) |
|
||||
| `-o` | Operator ID (see `operator list`) |
|
||||
| `-pr` | Protocol: `vless` or `hysteria2` |
|
||||
|
||||
To see available operators:
|
||||
|
||||
```bash
|
||||
python -m cli operator list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Enabling a profile
|
||||
|
||||
```bash
|
||||
python -m cli profile enable -i 8
|
||||
```
|
||||
|
||||
On first run, the profile has no subscription. The CLI will prompt:
|
||||
|
||||
```
|
||||
1) Request new subscription
|
||||
2) Enter billing code
|
||||
3) Exit
|
||||
```
|
||||
|
||||
Choose **1** to generate a billing code. The profile needs to be paid and activated before the connection is established.
|
||||
|
||||
---
|
||||
|
||||
### Dev / staging: bypass BTCPay payment
|
||||
|
||||
If the BTCPay server is not set up, manually activate the latest subscription:
|
||||
|
||||
```bash
|
||||
sudo docker compose exec laravel php artisan tinker --execute="
|
||||
\$sub = \App\Models\Subscription::orderBy('id', 'desc')->first();
|
||||
\$sub->expires_at = '2027-12-31 23:59:59';
|
||||
\$sub->duration = 720;
|
||||
\$sub->payment_reference = 'bypass_' . uniqid();
|
||||
\$sub->save();
|
||||
echo 'OK' . PHP_EOL;
|
||||
"
|
||||
```
|
||||
|
||||
Then enable again:
|
||||
|
||||
```bash
|
||||
python -m cli profile enable -i 8
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Other useful commands
|
||||
|
||||
```bash
|
||||
# List all profiles
|
||||
python -m cli profile list
|
||||
|
||||
# Show a specific profile
|
||||
python -m cli profile show -i 8
|
||||
|
||||
# Disable a profile
|
||||
python -m cli profile disable -i 8
|
||||
|
||||
# Destroy a profile
|
||||
python -m cli profile destroy -i 8
|
||||
|
||||
# List available applications
|
||||
python -m cli application list
|
||||
|
||||
# Sync data from server
|
||||
python -m cli sync
|
||||
|
||||
# Get current connection type
|
||||
python -m cli get connection
|
||||
|
||||
# Set connection type
|
||||
python -m cli set connection tor
|
||||
python -m cli set connection system
|
||||
```
|
||||
---
|
||||
|
||||
## Español
|
||||
|
||||
### ¿Qué es esto?
|
||||
|
||||
Una herramienta de línea de comandos para gestionar perfiles VPN y conexiones proxy encriptadas. Soporta dos tipos de conexión:
|
||||
|
||||
- **WireGuard** — VPN a nivel de sistema ligada a una ubicación específica
|
||||
- **Operator Proxy** — proxy encriptado (vless o hysteria2) ligado a un operador
|
||||
|
||||
---
|
||||
|
||||
### Instalación
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
python -m cli --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Resumen de comandos
|
||||
|
||||
| Comando | Descripción |
|
||||
|---|---|
|
||||
| `profile` | Gestionar perfiles de conexión |
|
||||
| `application` | Gestionar aplicaciones soportadas |
|
||||
| `operator` | Listar operadores disponibles |
|
||||
| `policy` | Gestionar políticas del sistema |
|
||||
| `get` / `set` | Leer / escribir configuración |
|
||||
| `sync` | Sincronizar datos del servidor |
|
||||
| `update` | Actualizar el cliente |
|
||||
|
||||
---
|
||||
|
||||
### Crear un perfil
|
||||
|
||||
#### WireGuard (VPN a nivel de sistema)
|
||||
|
||||
```bash
|
||||
python -m cli profile create system -i 2 -n "mi-vpn" -l de:be
|
||||
```
|
||||
|
||||
| Argumento | Descripción |
|
||||
|---|---|
|
||||
| `-i` | ID del perfil (número único) |
|
||||
| `-n` | Nombre del perfil (cualquier etiqueta) |
|
||||
| `-l` | Ubicación en formato `país:ciudad` |
|
||||
|
||||
#### Operator Proxy
|
||||
|
||||
```bash
|
||||
python -m cli profile create operator -i 8 -n "zenaku-vless" -o 3 -pr vless
|
||||
```
|
||||
|
||||
| Argumento | Descripción |
|
||||
|---|---|
|
||||
| `-i` | ID del perfil (número único) |
|
||||
| `-n` | Nombre del perfil (cualquier etiqueta) |
|
||||
| `-o` | ID del operador (ver `operator list`) |
|
||||
| `-pr` | Protocolo: `vless` o `hysteria2` |
|
||||
|
||||
Para ver los operadores disponibles:
|
||||
|
||||
```bash
|
||||
python -m cli operator list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Activar un perfil
|
||||
|
||||
```bash
|
||||
python -m cli profile enable -i 8
|
||||
```
|
||||
|
||||
La primera vez el perfil no tiene suscripción. El CLI mostrará:
|
||||
|
||||
```
|
||||
1) Request new subscription
|
||||
2) Enter billing code
|
||||
3) Exit
|
||||
```
|
||||
|
||||
Elige **1** para generar un billing code. El perfil debe ser pagado y activado antes de que se establezca la conexión.
|
||||
|
||||
---
|
||||
|
||||
### Dev / staging: saltarse el pago de BTCPay
|
||||
|
||||
Si el servidor BTCPay no está configurado, activa manualmente la última suscripción:
|
||||
|
||||
```bash
|
||||
sudo docker compose exec laravel php artisan tinker --execute="
|
||||
\$sub = \App\Models\Subscription::orderBy('id', 'desc')->first();
|
||||
\$sub->expires_at = '2027-12-31 23:59:59';
|
||||
\$sub->duration = 720;
|
||||
\$sub->payment_reference = 'bypass_' . uniqid();
|
||||
\$sub->save();
|
||||
echo 'OK' . PHP_EOL;
|
||||
"
|
||||
```
|
||||
|
||||
Luego activa el perfil de nuevo:
|
||||
|
||||
```bash
|
||||
python -m cli profile enable -i 8
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Otros comandos útiles
|
||||
|
||||
```bash
|
||||
# Listar todos los perfiles
|
||||
python -m cli profile list
|
||||
|
||||
# Ver un perfil específico
|
||||
python -m cli profile show -i 8
|
||||
|
||||
# Desactivar un perfil
|
||||
python -m cli profile disable -i 8
|
||||
|
||||
# Eliminar un perfil
|
||||
python -m cli profile destroy -i 8
|
||||
|
||||
# Listar aplicaciones disponibles
|
||||
python -m cli application list
|
||||
|
||||
# Sincronizar datos del servidor
|
||||
python -m cli sync
|
||||
|
||||
# Ver tipo de conexión actual
|
||||
python -m cli get connection
|
||||
|
||||
# Cambiar tipo de conexión
|
||||
python -m cli set connection tor
|
||||
python -m cli set connection system
|
||||
```
|
||||
|
|
@ -48,6 +48,11 @@ def register(subparsers):
|
|||
system_parser.add_argument('--location', '-l', default='')
|
||||
system_parser.add_argument('--connection', '-c', dest='connection_type', choices=['wireguard'], default='wireguard')
|
||||
|
||||
operator_parser = create_subs.add_parser('operator', parents=[base])
|
||||
operator_parser.add_argument('--name', '-n', default='')
|
||||
operator_parser.add_argument('--operator-id', '-o', type=int, required=True)
|
||||
operator_parser.add_argument('--protocol', '-pr', choices=['vless', 'hysteria2'], required=True)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
|
@ -68,6 +73,13 @@ def handle(arguments, main_parser):
|
|||
pprint.pp(ProfileController.get(arguments.id))
|
||||
|
||||
elif arguments.subcommand == 'create':
|
||||
|
||||
if arguments.profile_type == 'operator':
|
||||
connection = SystemConnection('operator', arguments.operator_id, arguments.protocol)
|
||||
profile = SystemProfile(arguments.id, arguments.name, None, None, connection)
|
||||
ProfileController.create(profile, profile_observer=profile_observer)
|
||||
return
|
||||
|
||||
location_details = parse_location_argument(arguments.location)
|
||||
location = LocationController.get(location_details.get('country_code'), location_details.get('code'))
|
||||
|
||||
|
|
@ -110,7 +122,6 @@ def handle(arguments, main_parser):
|
|||
else:
|
||||
main_parser.error('the following argument should be a valid reference: --id/-i')
|
||||
|
||||
|
||||
def _handle_subscription_error(exception, profile, ignore, arguments, main_parser):
|
||||
if type(exception).__name__ == 'InvalidSubscriptionError':
|
||||
print('The profile\'s subscription appears to be invalid.\n')
|
||||
|
|
@ -129,17 +140,24 @@ def _handle_subscription_error(exception, profile, ignore, arguments, main_parse
|
|||
|
||||
if manage_subscription_input in ('1', ''):
|
||||
print('\nCreating subscription...\n')
|
||||
|
||||
subscription_plan = SubscriptionPlanController.get(profile.connection, 720)
|
||||
if subscription_plan is None:
|
||||
raise RuntimeError('No compatible subscription plan was found. Please contact support.')
|
||||
|
||||
potential_subscription = SubscriptionController.create(subscription_plan, profile, connection_observer=connection_observer)
|
||||
if potential_subscription is None:
|
||||
raise RuntimeError('The subscription could not be created. Please try again later.')
|
||||
|
||||
ProfileController.attach_subscription(profile, potential_subscription)
|
||||
subscription = InvoiceController.handle_payment(potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer)
|
||||
if subscription is None:
|
||||
raise RuntimeError('The subscription could not be activated. Please try again later.')
|
||||
ProfileController.attach_subscription(profile, subscription)
|
||||
|
||||
# Operator no necesita invoice
|
||||
if potential_subscription.operator_id is None:
|
||||
subscription = InvoiceController.handle_payment(potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer)
|
||||
if subscription is None:
|
||||
raise RuntimeError('The subscription could not be activated. Please try again later.')
|
||||
ProfileController.attach_subscription(profile, subscription)
|
||||
|
||||
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
|
||||
|
||||
elif manage_subscription_input == '2':
|
||||
|
|
@ -158,7 +176,6 @@ def _handle_subscription_error(exception, profile, ignore, arguments, main_parse
|
|||
else:
|
||||
print('\nInput appears to be invalid. Please try again.\n')
|
||||
|
||||
|
||||
def _build_ignore(arguments):
|
||||
ignore = []
|
||||
if getattr(arguments, 'without_connection_protection', False):
|
||||
|
|
|
|||
Loading…
Reference in a new issue