Instalación
ExyonQ se instala como un binario único más un servicio systemd. Para publicar en los puertos 80 y 443 se ejecutan dos procesos hermanos con configuraciones distintas. Esta página cubre la compilación, el despliegue, esas dos unidades, los permisos y el primer arranque.
Compilar desde fuentes
El repositorio fija el canal de Rust en rust-toolchain.toml (1.99.1). Construir el producto y el CLI de control:
cargo build -p exyonq -p exyonqctl --release --locked
Comprobar el árbol completo antes de empaquetar:
cargo check --workspace --locked
cargo test --workspace --locked
Las compilaciones con el proveedor HTTP/3 quiche necesitan paquetes del host: cmake, clang y libclang-dev. El proveedor por defecto es s2n; quiche es opcional y existe un rollback quinn-legacy. El asignador por defecto es el del sistema, con una feature opcional allocator-jemalloc en el binario exyonq. Mimalloc no es una característica del producto.
Artefactos publicados
| Artefacto | Contenido |
|---|---|
exyonq-0.9.0-linux-{amd64,arm64}.tar.gz | Binarios exyonq y exyonqctl para esa arquitectura |
SHA256SUMS.txt | Sumas SHA-256 de los tarballs |
sbom.cdx.json | SBOM en CycloneDX |
THIRD_PARTY_NOTICES.md | Licencias y avisos de terceros |
ghcr.io/exyonq/exyonq:0.9.0, ghcr.io/exyonq/exyonq-wordpress:0.9.0 | Imágenes OCI (sin :latest) |
El release 0.9.0 publica estos artefactos: no incluye paquetes .deb/.rpm ni builds para macOS o Windows, así que el despliegue se hace con el tarball y una unidad systemd propia (más abajo). Las imágenes OCI están en GHCR (ghcr.io/exyonq/exyonq:0.9.0 y ghcr.io/exyonq/exyonq-wordpress:0.9.0).
Instalar el binario a mano
sudo install -m 0755 exyonq exyonqctl /usr/bin/
sudo install -d -m 0750 -o exyonq -g exyonq /etc/exyonq /var/lib/exyonq /var/log/exyonq /run/exyonq
Tras el primer arranque, systemd-tmpfiles --create recrea /run/exyonq, /var/lib/exyonq y /var/log/exyonq con propietario exyonq y modo 0750.
El patrón de dos procesos
El despliegue estándar no ejecuta un proceso que escuche en :80 y :443 a la vez. Ejecuta dos instancias del mismo binario, cada una con su TOML y su socket de control:
# /etc/systemd/system/exyonq-http.service — :80, solo redirecciones y retos ACME
[Unit]
Description=ExyonQ HTTP to HTTPS redirect
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/usr/local/bin/exyonq serve -c /etc/exyonq/http.toml
Restart=on-failure
RestartSec=2
Environment=EXYONQ_ACME_CHALLENGE_DIR=/var/lib/exyonq-http01
Environment=EXYONQ_CONTROL_SOCKET=/run/exyonq/http.sock
RuntimeDirectory=exyonq
[Install]
WantedBy=multi-user.target
La unidad del 443 es idéntica salvo ExecStart=/usr/local/bin/exyonq serve -c /etc/exyonq/https.toml y EXYONQ_CONTROL_SOCKET=/run/exyonq/https.sock.
Por qué no se fusionan
La identidad de una ruta es (host, tipo de match, patrón) y es global en toda la configuración, no por listener. Si el mismo fichero declarase la ruta de redirección / para exyonq.org en :80 y la ruta de contenido / para exyonq.org en :443, ambas tendrían la misma identidad: la de redirección taparía a la de contenido y el resultado observado es un bucle (https://host respondiendo Location: https://host). Fusionar los dos TOML obligaría a diseñar un enrutado por listener que el producto no tiene.
Con dos procesos el reparto es limpio: el del :80 solo redirige, el del :443 es el único listener público con contenido. Cada uno mantiene su socket de control, así que se recargan por separado (exyonqctl reload -s /run/exyonq/http.sock, .../https.sock).
El único dato que ambos comparten es el directorio de retos ACME (EXYONQ_ACME_CHALLENGE_DIR). El proceso del :443 registra el token, el del :80 lo sirve porque lo lee del mismo directorio. Ese directorio debe quedar fuera de RuntimeDirectory: systemd borra un RuntimeDirectory al parar cualquiera de las unidades que lo declaran, y se llevarían por delante el socket del hermano. Usa una ruta estable, por ejemplo /var/lib/exyonq-http01 con modo 0750 y propietario del usuario del servicio.
Permisos
- El servicio corre como usuario sin privilegios (
www-dataen el despliegue de referencia,exyonqsi usas la unidad del paquete). - Para abrir puertos por debajo de 1024 sin ser root:
AmbientCapabilities=CAP_NET_BIND_SERVICEyCapabilityBoundingSet=CAP_NET_BIND_SERVICE. Si solo escuchas en puertos altos, puedes cerrar el conjunto de capacidades. - La clave privada debe ser legible por el usuario del servicio y no por el resto. En el despliegue de referencia el directorio TLS pertenece al grupo del servicio y las claves están en 0600 o 0640; el certificado puede ser legible por todos, la clave no.
- ACME escribe el certificado y la cuenta en el mismo directorio que
tls.cert, así que ese directorio debe ser escribible por el usuario del servicio. - La unidad del paquete endurece el proceso:
ProtectSystem=strict,ProtectHome=true,NoNewPrivileges=true,ReadOnlyPaths=/etc/exyonqyReadWritePaths=/var/lib/exyonq /var/log/exyonq /run/exyonq. La configuración es de solo lectura en ejecución. Si activas[logging.file], escribe bajo/var/log/exyonq.
Primer arranque
- Valida la configuración (siempre antes de instalar o recargar):
exyonq validate -c /etc/exyonq/https.toml
- Arranca el servicio y sigue el journal:
systemctl start exyonq-sites && journalctl -u exyonq-sites -f
- Comprueba que responde y que el estado del proceso es correcto:
curl -sS -o /dev/null -w '%{http_code}\n' https://exyonq.org/health
exyonqctl status --socket /run/exyonq/https.sock
La salida de status incluye process_state, generation, fingerprint, uptime_s, active_connections, draining y version.
Arranque sin certificado
Desde 0.9.0 el proceso del :443 puede arrancar sin certificado en disco: responde los retos HTTP-01 en el listener en claro (el del :80, con el directorio de retos compartido), instala el certificado emitido y reconstruye el aceptador TLS sin reiniciar. Mientras el material no está disponible, los listeners TLS fallan cerrados: no sirven tráfico con un certificado inválido ni con el de otro host.
Validar antes de instalar
La unidad del paquete ejecuta la validación como ExecStartPre, de modo que un error de configuración impide el arranque en lugar de dejar el servicio en bucle:
ExecStartPre=/usr/bin/exyonq validate -c ${EXYONQ_CONFIG}
ExecStart=/usr/bin/exyonq serve -c ${EXYONQ_CONFIG}
ExecReload=/usr/bin/exyonqctl reload --config ${EXYONQ_CONFIG} --socket ${EXYONQ_CONTROL_SOCKET}
validate sale con código 0 e imprime config OK: <ruta> seguido de OK; con un error sale con código 1 e imprime un diagnóstico error[EXY-CONFIG-…] con la línea y el campo culpables. El comando avisa de que es transitorio y recomienda exyonqctl config lint o exyonqctl config test.
La unidad del paquete no usa
sd_notify: el demonio no lo implementa. No configuresType=notifyniNotifyAccess. La parada ordenada esSIGTERM, conTimeoutStopSecholgado (90 s en la unidad publicada).