estratos / hestia-symfony
Hestia Control Panel REST API client for Symfony
Package info
github.com/estratos/hestia-symfony
Type:symfony-bundle
pkg:composer/estratos/hestia-symfony
Requires
- php: >=8.1
- ext-curl: *
- symfony/config: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
Requires (Dev)
- phpunit/phpunit: ^10.5 || ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Cliente PHP para la API REST de Hestia Control Panel con integración para Symfony. Permite gestionar usuarios, dominios web, correo y DNS desde una aplicación Symfony o desde PHP standalone.
Versión 0.2.0. El cliente está desacoplado del bundle, se registra como servicio con configuración validada, soporta access/secret keys y usa TLS verificado por defecto. Si
0.2.0todavía no está en Packagist, instaladev-main; consulta Migración desde 0.1.x.
Tabla de contenidos
- Características
- Requisitos
- Instalación
- Configuración
- Uso
- Referencia de la API
- Autenticación
- Errores y excepciones
- Seguridad
- Desarrollo y pruebas
- Migración desde 0.1.x
- Licencia
Características
- Cliente desacoplado (
HestiaClient) y bundle (HestiaSymfonyBundle) independientes. - Inyección de dependencias con configuración validada (
config/packages/hestia.yaml). - Autenticación por usuario/contraseña o por access/secret keys.
- TLS verificado por defecto, timeouts configurables y
User-Agentpropio. - Listados devuelven arrays; las mutaciones usan
returncode=yesy lanzan excepciones. - Transporte HTTP inyectable (
HestiaTransportInterface) para tests. - Suite de tests con PHPUnit.
Requisitos
| Componente | Requisito |
|---|---|
| PHP | >= 8.1 con ext-curl |
| Symfony | >= 6.4 (LTS) o 7.x |
| Hestia CP | API habilitada, puerto 8083 accesible y la IP de la aplicación permitida |
| Credenciales | Contraseña del administrador o un par access/secret key |
Instalación
composer require estratos/hestia-symfony:^0.2
Mientras 0.2.0 no esté publicado en Packagist:
composer require estratos/hestia-symfony:dev-main
Configuración
1. Registrar el bundle
<?php // config/bundles.php return [ // ... Estratos\HestiaApi\HestiaSymfonyBundle::class => ['all' => true], ];
2. Configurar la conexión
Con usuario y contraseña:
# config/packages/hestia.yaml hestia: host: '%env(HESTIA_HOST)%' port: 8083 username: admin password: '%env(HESTIA_PASSWORD)%' verify_ssl: true connect_timeout: 5 timeout: 30
Con access/secret keys (recomendado por Hestia):
# config/packages/hestia.yaml hestia: host: '%env(HESTIA_HOST)%' port: 8083 access_key: '%env(HESTIA_ACCESS_KEY)%' secret_key: '%env(HESTIA_SECRET_KEY)%' verify_ssl: true
Variables de entorno:
# .env.local (no lo versiones) HESTIA_HOST=panel.example.com HESTIA_PASSWORD="cambia-esto" # o, si usas access keys: # HESTIA_ACCESS_KEY=... # HESTIA_SECRET_KEY=...
HESTIA_HOST debe ser solo el nombre o la IP, sin https:// ni ruta. HestiaConfig valida los datos al construir el cliente y falla si no hay credenciales, si el host es inválido o si el puerto está fuera de rango.
3. Opciones de configuración
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
host |
string | — (obligatorio) | Nombre o IP del panel |
port |
int | 8083 |
Puerto de la API |
username |
string | admin |
Usuario para autenticación por contraseña |
password |
string|null | null |
Contraseña del administrador |
access_key |
string|null | null |
Access key de Hestia |
secret_key |
string|null | null |
Secret key de Hestia |
verify_ssl |
bool | true |
Verifica el certificado TLS |
connect_timeout |
int | 5 |
Timeout de conexión en segundos |
timeout |
int | 30 |
Timeout total en segundos |
user_agent |
string | hestia-symfony/0.2 |
User-Agent de las peticiones |
Si configuras contraseña y access keys a la vez, el cliente usa las access keys.
4. Uso sin el bundle
<?php use Estratos\HestiaApi\Client\HestiaClient; use Estratos\HestiaApi\Client\HestiaConfig; $client = new HestiaClient(new HestiaConfig( host: 'panel.example.com', username: 'admin', password: $_ENV['HESTIA_PASSWORD'] ?? null, ));
Con access keys:
$client = new HestiaClient(new HestiaConfig( host: 'panel.example.com', accessKey: $_ENV['HESTIA_ACCESS_KEY'] ?? null, secretKey: $_ENV['HESTIA_SECRET_KEY'] ?? null, ));
Uso
Inyectar el cliente
<?php namespace App\Controller; use Estratos\HestiaApi\Client\HestiaClient; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; final class HestiaController extends AbstractController { public function __construct(private readonly HestiaClient $hestia) { } #[Route('/hestia/usuarios', methods: ['GET'])] public function usuarios(): JsonResponse { return $this->json($this->hestia->listUsers()); } }
Usuarios
$users = $hestia->listUsers(); $user = $hestia->listUser('cliente01'); $hestia->addUser('cliente01', 'S3gura!Pass', 'cliente@example.com', 'default'); $hestia->suspendUser('cliente01'); // restart "no" por defecto $hestia->unsuspendUser('cliente01');
Dominios web
$domains = $hestia->listDomains('cliente01'); $domain = $hestia->listDomain('cliente01', 'example.com'); $hestia->addDomain('cliente01', 'example.com'); $hestia->suspendDomain('cliente01', 'example.com'); $hestia->unsuspendDomain('cliente01', 'example.com');
Correo
$mailDomains = $hestia->listMailDomains('cliente01'); $accounts = $hestia->listMailAccounts('cliente01', 'example.com'); $hestia->addMailaccount('cliente01', 'example.com', 'info', 'S3gura!Pass', 'unlimited'); $hestia->changeMailaccountPassword('cliente01', 'example.com', 'info', 'NuevaPass!');
DNS
$zones = $hestia->listDnsDomains('cliente01'); $records = $hestia->listDnsRecords('cliente01', 'example.com'); $hestia->addDnsZone( 'cliente01', 'example.com', '203.0.113.10', 'ns1.example.com', 'ns2.example.com', ); $hestia->addDnsRecord('cliente01', 'example.com', 'www', 'A', '203.0.113.10'); // Sincroniza todos los hosts del clúster, o solo uno: $hestia->syncCluster(); $hestia->syncCluster('dns2.example.com');
Comandos no envueltos
call() construye la autenticación y ejecuta cualquier comando de Hestia:
// Respuesta cruda, sin decodificar: $raw = $hestia->call('v-list-users', ['plain']); // Código de salida: "0" significa éxito. $exitCode = $hestia->call('v-delete-user', ['cliente01'], true); if ($exitCode !== '0') { throw new \RuntimeException('Hestia devolvió el código ' . $exitCode); }
call() lanza excepción si Hestia responde con error HTTP o con un cuerpo que empieza por Error:, pero no valida los códigos numéricos: compruébalos tú cuando uses returnCode = true.
Referencia de la API
Todos los listados piden format=json y devuelven el JSON decodificado. Las mutaciones usan returncode=yes y devuelven void; si Hestia devuelve un código distinto de 0, lanzan HestiaApiException.
Listados
| Método | Comando Hestia | Devuelve |
|---|---|---|
listUser(string $username) |
v-list-user |
array<string, mixed> |
listUsers() |
v-list-users |
array<string, mixed> |
listUserPackages() |
v-list-user-packages |
array<string, mixed> |
listDomain(string $username, string $domain) |
v-list-web-domain |
array<string, mixed> |
listDomains(string $username) |
v-list-web-domains |
array<string, mixed> |
listMailDomains(string $username) |
v-list-mail-domains |
array<string, mixed> |
listMailDomain(string $username, string $domain) |
v-list-mail-domain |
array<string, mixed> |
listMailAccounts(string $username, string $domain) |
v-list-mail-accounts |
array<string, mixed> |
listDnsDomain(string $username, string $domain) |
v-list-dns-domain |
array<string, mixed> |
listDnsDomains(string $username) |
v-list-dns-domains |
array<string, mixed> |
listDnsRecords(string $username, string $domain) |
v-list-dns-records |
array<string, mixed> |
Mutaciones
| Método | Comando Hestia |
|---|---|
addUser(string $username, string $password, string $email, string $package = 'default') |
v-add-user |
suspendUser(string $username, string $restart = 'no') |
v-suspend-user |
unsuspendUser(string $username, string $restart = 'no') |
v-unsuspend-user |
addDomain(string $username, string $domain, ?string $ip = null) |
v-add-domain |
suspendDomain(string $username, string $domain, string $restart = 'no') |
v-suspend-web-domain |
unsuspendDomain(string $username, string $domain, string $restart = 'no') |
v-unsuspend-web-domain |
addMailaccount(string $username, string $domain, string $account, string $password, string|int $quota = 'unlimited') |
v-add-mail-account |
changeMailaccountPassword(string $username, string $domain, string $account, string $password) |
v-change-mail-account-password |
addDnsZone(string $username, string $domain, string $ip, ?string $ns1 = null, ?string $ns2 = null) |
v-add-dns-domain |
addDnsRecord(string $username, string $domain, string $record, string $type, string $value, ?int $priority = null) |
v-add-dns-record |
syncCluster(?string $host = null) |
v-sync-dns-cluster |
Infraestructura
| Método | Descripción |
|---|---|
call(string $command, array $arguments = [], bool $returnCode = false) |
Ejecuta un comando arbitrario y devuelve la respuesta cruda |
getConfig() |
Devuelve la HestiaConfig en uso |
Autenticación
Hestia mantiene la autenticación por usuario/contraseña como obsoleta y recomienda access/secret keys. Las access keys:
- Pueden limitarse a un conjunto de comandos mediante un perfil de API.
- Están asociadas a un usuario; en comandos que reciben un usuario, este debe coincidir con el de la clave cuando la clave no es de administrador.
- No dependen de la contraseña del administrador.
El cliente envía access_key y secret_key en el POST; si no están configuradas, envía user y password. No se admite el formato antiguo hash de la API.
Errores y excepciones
use Estratos\HestiaApi\Exception\HestiaApiException; use Estratos\HestiaApi\Exception\HestiaTransportException; try { $hestia->addUser('cliente01', 'S3gura!Pass', 'cliente@example.com'); } catch (HestiaTransportException $exception) { // Conexión, TLS o timeout. } catch (HestiaApiException $exception) { // Error de Hestia o HTTP >= 400. $code = $exception->getHestiaCode(); // 0-20, o null $status = $exception->getHttpStatus(); // null si no hubo error HTTP }
Códigos de Hestia: 0 OK, 1 faltan argumentos, 2 argumento inválido, 3 no existe, 4 ya existe, 8 límite del paquete, 9 contraseña incorrecta, 10 acceso denegado, 11 subsistema deshabilitado, 20 fallo al reiniciar un servicio. La tabla completa está en la documentación de Hestia.
Seguridad
- La verificación TLS está activada por defecto. Desactívala solo en desarrollo con
verify_ssl: falseoverifySsl: false. - Usa variables de entorno o un gestor de secretos; no guardes contraseñas ni access keys en el repositorio.
- Prefiere access keys con permisos mínimos frente a la contraseña del administrador.
- Autoriza únicamente las IPs de tu aplicación en la configuración de la API de Hestia; no uses
allow-all. - No expongas el puerto
8083sin firewall. - No registres los payloads de las peticiones: contienen credenciales.
Desarrollo y pruebas
git clone https://github.com/estratos/hestia-symfony.git cd hestia-symfony composer install composer test
La suite PHPUnit cubre la construcción de payloads (contraseña y access keys), las firmas de los métodos, los errores de Hestia y la configuración del bundle en un contenedor de Symfony.
Los pendientes y el roadmap están en docs/MEJORAS.md.
Migración desde 0.1.x
La versión 0.2.0 elimina Estratos\HestiaApi\HestiaApi y sustituye el uso directo por Estratos\HestiaApi\Client\HestiaClient. Los cambios principales:
- El bundle pasa a ser
HestiaSymfonyBundley el cliente se inyecta como servicio. addDomain($domain)→addDomain($username, $domain, $ip = null).listMailAccounts($domain)→listMailAccounts($username, $domain).addDnsRecord($username, $domain, $type, $value)→addDnsRecord($username, $domain, $record, $type, $value, $priority = null).addDnsZone($username, $domain, $type, $value)→addDnsZone($username, $domain, $ip, $ns1 = null, $ns2 = null).- Las suspensiones reciben
$restart(por defecto'no'), no un formato. - Los listados ya no aceptan
$format; usan JSON. Para otros formatos, usacall(). request($postvars)→call($command, $arguments, $returnCode).- Los métodos DNS devuelven arrays y las mutaciones lanzan excepciones.
Consulta UPGRADING.md para la guía completa y CHANGELOG.md para el detalle del release.
Licencia
MIT. Copyright (c) 2024 Estratos Consulting. Consulta LICENSE.