Search by

estratos / hestia-symfony

estratos

Hestia Control Panel REST API client for Symfony

Package info

github.com/estratos/hestia-symfony

Type:symfony-bundle

pkg:composer/estratos/hestia-symfony

Statistics

Installs: 26

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.2.0 2026-10-26 18:16 UTC

This package is auto-updated.

Last update: 2026-10-26 20:35:08 UTC


README

Packagist Version License PHP Version

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.0 todavía no está en Packagist, instala dev-main; consulta Migración desde 0.1.x.

Tabla de contenidos

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-Agent propio.
  • Listados devuelven arrays; las mutaciones usan returncode=yes y 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: false o verifySsl: 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 8083 sin 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 HestiaSymfonyBundle y 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, usa call().
  • 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.

Enlaces