hyn-me/webserver
Webserver integration for the Hyn multi-tenant Laravel package, providing the routing and request-handling layer needed to serve multiple tenant sites from a single installation. Configure hosts and let Hyn resolve the correct tenant per request.
Treat this as a reference implementation, not a batteries-included dependency. The repository carries no stars, no dependents, and no score, so assume the code is unmaintained until proven otherwise — read it before you require it.
Minimal steps:
hyn/multi-tenant major version, PHP version). Given the ecosystem around hyn-me, it almost certainly targets an older Laravel/multi-tenant pairing. If your app is on a current Laravel release, budget time for a fork rather than expecting a clean install.git clone into packages/ and read composer.json, src/, and config/ first. You are looking for three things: the service provider, the config file name, and any Artisan commands. Those tell you the entire public surface — this package is a thin wrapper around template rendering plus a webserver reload."repositories": [{ "type": "path", "url": "packages/webserver" }]
Then composer require hyn-me/webserver:@dev.php artisan vendor:publish --provider="Hyn\Webserver\WebserverServiceProvider" (verify the provider FQCN in src/; it is the single most common source of copy-paste errors here).First use case: generate a vhost for one tenant on your local machine and diff it against a hand-written config. Do not wire it into a deploy pipeline until you have read the rendered output line by line. If the generated file differs structurally from what you would write yourself, stop and decide whether to fork or template-patch before going further.
Treat vhost generation as a pure function. The healthiest way to use this package is to call it for its rendering capability and own the writing and reloading yourself. Render to a string, write with your own atomic-rename helper, and reload through your own deploy tooling. This insulates you from the package's file-layout assumptions and makes the whole thing testable without root.
Hook the tenant lifecycle, don't poll. Wire generation to tenant creation/deletion events rather than a cron sweep. A typical shape:
| Trigger | Action |
|---|---|
| Tenant created | Render vhost → validate → write → reload |
| Tenant domain added/changed | Re-render, same path (idempotent) |
| Tenant deleted | Remove vhost → reload |
| Deploy | Full reconcile pass for all tenants |
The reconcile pass matters: event-driven generation drifts the moment one job fails. Make the full pass the source of truth and the event hooks a fast path.
Always separate render → validate → activate. Three distinct steps, each independently failable:
render(template, tenant) → string
nginx -t (or apachectl configtest) → exit 0
reload (not restart) → exit 0
If validation fails, leave the previous file in place. Write to a temp path, validate against a symlinked staging dir, then atomic rename() into conf.d/. Never truncate-and-write the live file.
Put the reload behind a queue job with a lock. Web requests must never shell out to systemctl reload nginx. Dispatch a job, use Cache::lock() to serialise concurrent reload attempts, and rate-limit so a bulk tenant import doesn't trigger 500 reloads. A debounce pattern — mark "dirty", coalesce, reload once — is worth building on day one.
Use it in local development as a domain router. The most immediately valuable use: point it at your local nginx and generate one server block per tenant pointing at php-fpm/valet/Herd. Combined with a wildcard DNS entry (*.test → 127.0.0.1) this gives you real hostname-based tenant resolution locally instead of header hacks — which is the difference between catching host-resolution bugs in dev and catching them in production.
Integration tips:
WebserverManager interface with render(), write(), reload(). One adapter class today buys you a clean swap to hand-rolled templates or a maintained alternative later./etc/nginx.Zero adoption is the headline risk. No stars, no dependents, no score means: no issue triage, no security patches, no compatibility updates. Vendor the code into your repo (composer.json path repo or a fork you control) rather than depending on Packagist. Assume you own maintenance from the day you install it.
Privilege boundary. Reloading a webserver needs root or a narrowly-scoped sudoers rule. If the package's commands assume they run as root, do not expose them as Artisan commands callable from a web route or a compromised admin panel. Scope to sudo nginx -t && sudo systemctl reload nginx with NOPASSWD on exactly those binaries.
Tight coupling to hyn/multi-tenant internals. Expect direct reads of tenant models, hostname tables, and directory conventions. Any upgrade to the multi-tenant package — or a change to how you store tenant hostnames — can silently break generation. Pin the multi-tenant version and add an integration test that creates a real tenant and asserts the rendered output.
Config caching bites hard. env() calls inside a published config file return null after php artisan config:cache. If generated paths go missing in production but work locally, this is almost always why. Use config() values seeded from a dedicated config file, and remember config:clear after every publish.
Path and socket assumptions. Templates hardcode things like /var/www/{tenant}/public, unix:/run/php/php8.2-fpm.sock, and log paths. These differ across distros, PHP versions, and container images. Find every hardcoded path on your first read and move them into config. PHP-FPM socket path drift after a PHP upgrade is the classic silent 502.
Reload vs restart, and the reload that silently no-ops. systemctl reload nginx exits 0 even when the config was rejected in some setups; always run nginx -t first and check its exit code separately. Also confirm the reload actually picked up the file — a config written to a directory not covered by an include directive will look fine and do nothing.
Tenant-supplied values are untrusted. Domain names, aliases, and document roots flow into a templated config file and sometimes into shell commands. A tenant able to set a hostname containing ; or a newline is a template-injection and potentially command-injection vector. Sanitise and whitelist hostnames (^[a-z0-9.-]+$) before rendering, and never interpolate tenant data into a shell string — use argument arrays.
Debugging workflow:
php artisan tinker and dd() the string.include, wrong socket, or wrong root.nginx -t output and /var/log/nginx/error.log before touching application logs; a bad vhost presents as a 502, not an app error.Extension points to look for: the template/driver class (swap or subclass for a custom webserver or a different PHP-FPM setup), the config file (paths, driver selection, reload command), and the service provider bindings (rebind the renderer to your own implementation). If none of these are cleanly overridable, that settles the fork-vs-depend question.
SSL is likely out of scope. Do not assume certificate provisioning or renewal is handled. Plan for certbot or your ingress controller to own certs, and make sure generated configs don't fight with whatever manages the listen 443 blocks.
How can I help you explore Laravel packages today?