# Cbox > Cbox builds open source infrastructure tools for PHP teams. Queue autoscaling, system metrics, PHP-FPM monitoring, health checks, and a managed platform for Laravel applications. --- --- title: "About" description: "Cbox is Sylvester Damgaard. Writing code and running servers since 2000. Open source packages, consulting, and Cortex." url: "https://cbox.dk/about" --- --- --- title: "Services" description: "PHP infrastructure consulting and development services. Architecture reviews, performance optimization, and DevOps for Laravel teams." url: "https://cbox.dk/services" --- # Selective work for the right projects. I work full-time and take on a small number of projects that fit. One person, no agency. I have been doing this since 2000. [Get in touch](https://cbox.dk/contact) ## Consulting Architecture review, technology strategy, technical due diligence. I have been the CTO making those calls and can give you a straight answer on your stack. [Talk to me](https://cbox.dk/contact) ## Development Custom Laravel and Statamic applications. I write the code, set up the tests, and make sure it runs well when it ships. [Start a project](https://cbox.dk/contact) ## DevOps & Infrastructure Kubernetes clusters, CI/CD pipelines, container orchestration, monitoring, and autoscaling. The stuff that keeps your app running. [Get help](https://cbox.dk/contact) ## Got the right project? Let's talk. I am straightforward about whether I can help and what it takes. If the fit is right, I move fast. [Get in touch](https://cbox.dk/contact) --- ## Packages --- title: "Billing Engine for Laravel" description: "A gateway-agnostic billing engine for Laravel: catalog, subscriptions, real-time usage metering with hard limits, a double-entry ledger, wallets and credits, invoicing and pricing. The framework peer to laravel-id." github: "https://github.com/cboxdk/laravel-billing" packagist: "cboxdk/laravel-billing" language: "PHP" categories: - billing - laravel - php url: "https://cbox.dk/packages/laravel-billing" docs: "https://cbox.dk/packages/laravel-billing/docs/v0.md" --- # Billing Engine for Laravel A gateway-agnostic billing engine for Laravel: catalog, subscriptions, real-time usage metering with hard limits, a double-entry ledger, wallets and credits, invoicing and pricing. The framework peer to laravel-id. Billing Engine for Laravel is a gateway-agnostic library of billing primitives you compose into a billing product: catalog, subscriptions, real-time usage metering with hard limits, a double-entry ledger, wallets and credits, invoicing and pricing. It is the framework peer to Identity for Laravel: UI-free and domain-free, with every capability behind a contract you bind, mock or replace. This is the package, not the product. The self-hostable app built on it is Cbox Billing. ### Billing Primitives - **Real-Time Metering**: Reserve then commit usage with hard limits that block in sub-milliseconds. Lease-backed local enforcement refills from an allowance source, so there is no cross-node overspend and no shared Redis required. - **Metering Truth**: An immutable, append-only usage event log. Invoices are recomputed from it with the pinned price, never read from a disposable counter. - **Double-Entry Ledger**: Money lives in a double-entry ledger where balances derive from immutable postings, so the books always balance and nothing is edited in place. - **Catalog & Subscriptions**: Products, plans and subscriptions modelled as first-class primitives, with quotes, invoicing, payments and refunds behind contracts. Plan retirement gives a discontinued plan a hard cutoff, moving existing subscribers to a successor at their next renewal instead of letting them sit on it forever. - **Wallets & Credits**: Prepaid balances and credits handled as ledger postings, so grants, top-ups and consumption are auditable like everything else. - **Contracts-First**: Every module sits behind a contract you bind, mock or replace, with dogfooded testing traits and fakes for metering and the allowance source. > **Three separate concerns**: Enforcement, metering truth and money are kept apart. An app-local counter answers may-this-proceed in sub-ms and is rebuildable from the log. The immutable usage log is the source of truth. Money is a double-entry ledger. The invoice is computed from the event log, never read from a counter. ## Installation ```bash composer require cboxdk/laravel-billing ``` ## Documentation Full documentation: [Billing Engine for Laravel Docs](https://cbox.dk/packages/laravel-billing/docs/v0.md) ## Resources - [Double-entry bookkeeping](https://en.wikipedia.org/wiki/Double-entry_bookkeeping) --- --- title: "Auth & identity framework for Laravel" description: "A Laravel-native auth and identity framework you bind into your own app. Central login, enterprise SSO (SAML/OIDC), SCIM directory sync, RBAC, billing-fed entitlements and a tamper-evident audit trail, all interface-driven and deny-by-default." github: "https://github.com/cboxdk/laravel-id" packagist: "cboxdk/laravel-id" language: "PHP" categories: - security - laravel url: "https://cbox.dk/packages/laravel-id" docs: "https://cbox.dk/packages/laravel-id/docs/v1.md" --- # Auth & identity framework for Laravel A Laravel-native auth and identity framework you bind into your own app. Central login, enterprise SSO (SAML/OIDC), SCIM directory sync, RBAC, billing-fed entitlements and a tamper-evident audit trail, all interface-driven and deny-by-default. Laravel ID is an auth and identity framework you bind into your own app: central login, enterprise SSO, directory sync, RBAC, billing-driven entitlements and a tamper-evident audit trail. It is UI-free and domain-free. Every capability sits behind a contract you bind, mock, extend or replace, and the whole thing is deny-by-default and verified with tests, PHPStan at max level and composer audit before it ships. ### What It Covers - **Authentication**: Central login and user directory behind a UserDirectory contract, with password and credential handling that stays out of your domain code. - **SSO & Federation**: A SAML 2.0 identity provider with signed assertions and XSW hardening, a SAML and OIDC relying party with replay protection and JIT provisioning, and an OAuth 2.0 / OIDC back-channel of your own. - **Directory Sync (SCIM)**: Inbound SCIM 2.0 for Users and Groups plus Google Workspace and Microsoft Entra pull connectors, with deprovisioning that revokes sessions immediately. Outbound provisioning is a generic SCIM client with an outbox, retries and a circuit breaker, users only. - **Access Control & RBAC**: Org-scoped RBAC with hierarchy roll-down and entitlements, deny-by-default, so every check is explicit rather than assumed. A ReBAC engine and PDP sit alongside it. - **MFA & Credentials**: Recovery codes, magic links, password reset and a password policy covering length, reuse, expiry and lockout, with TOTP, WebAuthn passkeys and email OTP on top. Breach screening and SMS OTP are contracts you bind. - **Audit & Governance**: A SHA-256 hash-chained audit trail you can query and stream to a SIEM, plus access-certification campaigns and segregation-of-duties checks over roles and memberships. ## Contracts all the way down The framework is split into kernels (Tenancy, Crypto, Audit, Events, Authorization, Usage) and domain modules (Organization, Identity, Otp, AccessControl, Directory, Provisioning, Federation, SamlIdp, OAuthServer, Governance, TokenVault, ExternalActions, Webhooks, AuditQuery, AuditStreaming). Each is interface-driven: you resolve a contract like Organizations or UserDirectory from the container and get a working implementation, which you can swap for your own without touching the call sites. > **Standards and compliance**: The docs carry an RFC-by-RFC matrix for OAuth, OIDC, SCIM, SAML, WebAuthn and MCP, grading every row as full, partial, contract-only or not implemented rather than claiming blanket support, plus which parts of the FAPI 2.0 baseline are switchable today. Controls map to SOC 2, ISO 27001, NIS2, GDPR, HIPAA and PCI-DSS, with a threat model alongside. > **It does not serve /authorize**: This package ships the back-channel: token, introspection, revocation, registration, PAR, device, CIBA, discovery, JWKS, and the crypto and validation behind them. Login, consent, and everything decided at the authorization request are your app's to build. The deployable Cbox ID app implements that half if you would rather not. The package is 0.x, so read UPGRADING.md before crossing a version. ## Installation ```bash composer require cboxdk/laravel-id ``` ## Documentation Full documentation: [Auth & identity framework for Laravel Docs](https://cbox.dk/packages/laravel-id/docs/v1.md) ## Resources - [OpenID Connect Core specification](https://openid.net/specs/openid-connect-core-1_0.html) - [SCIM 2.0 (RFC 7644)](https://www.rfc-editor.org/rfc/rfc7644) --- --- title: "Operations for Laravel" description: "Durable, multi-step operations for Laravel. Track a deploy, a provisioning run or an import as a first-class record with a strict state machine, per-step progress, idempotent retries and a stall sweep, instead of a black-box job." github: "https://github.com/cboxdk/laravel-operations" packagist: "cboxdk/laravel-operations" language: "PHP" categories: - infrastructure - laravel url: "https://cbox.dk/packages/laravel-operations" docs: "https://cbox.dk/packages/laravel-operations/docs/v0.md" --- # Operations for Laravel Durable, multi-step operations for Laravel. Track a deploy, a provisioning run or an import as a first-class record with a strict state machine, per-step progress, idempotent retries and a stall sweep, instead of a black-box job. Operations for Laravel is the engine behind long-running background work. Track a deploy, a provisioning run or an import as a first-class record with a strict state machine, per-step progress, idempotent retries and a stall sweep, instead of a black-box job. Background work that spans several steps usually collapses to a single job with no visible progress and no way to tell stuck from slow. This gives that work an auditable status, per-step timing, and a deadline so a crashed worker self-heals into a terminal failed rather than a stuck running forever. ### What It Does - **Strict State Machine**: An operation moves pending to running to completed or failed. Illegal transitions throw, terminal states never move, and the status is auditable at every point. - **Per-Step Progress**: Declare the steps up front and advance them one by one. Each step carries its own timing, so a client can see exactly where a multi-step run is rather than a single opaque job. - **Idempotent Retries**: Re-running a step or a completion is a safe no-op, so at-least-once workers never double-apply. Retries are safe by construction, not by luck. - **Stall Sweep**: Give an operation a deadline. The operations:sweep-stalled command moves anything past its deadline to failed, so a crashed worker self-heals instead of leaving a run stuck forever. - **Live Progress, No Polling**: Every change dispatches an OperationUpdated event, a plain Laravel event you wire to your own transport, so a client can watch progress live without polling. - **Dogfooded Testing**: Ships InteractsWithOperations and FakeOperations. Fake the engine in your own suite and assert an operation started, advanced or completed, the same way you test mail. > **A deadline that self-heals**: Pass expiresAt when you start an operation and schedule operations:sweep-stalled to run every minute. A run that blows past its deadline is swept to failed automatically, so a stuck running state cannot linger after a worker dies mid-flight. ## Contracts, not a concrete manager Depend on the Operations contract, never the concrete manager, so you can decorate it and tests can swap the bundled fake. The state machine is deny-by-default: illegal transitions throw and terminal states are final. It is tenancy-agnostic, tenant_id is an optional column the package simply persists, bring your own scoping, and broadcast-agnostic, OperationUpdated is a plain event you route to whatever transport drives your live UI. ## Installation ```bash composer require cboxdk/laravel-operations ``` ## Documentation Full documentation: [Operations for Laravel Docs](https://cbox.dk/packages/laravel-operations/docs/v0.md) ## Resources - [Finite-state machine](https://en.wikipedia.org/wiki/Finite-state_machine) --- --- title: "Postal integration for Laravel" description: "Postal mail server integration for Laravel: a typed multi-server send transport, verified webhooks for delivery, bounce, open, click and DNS events, inbound email and an idempotent per-message status store." github: "https://github.com/cboxdk/laravel-postal" packagist: "cboxdk/laravel-postal" language: "PHP" categories: - laravel - infrastructure - observability url: "https://cbox.dk/packages/laravel-postal" docs: "https://cbox.dk/packages/laravel-postal/docs/v0.md" --- # Postal integration for Laravel Postal mail server integration for Laravel: a typed multi-server send transport, verified webhooks for delivery, bounce, open, click and DNS events, inbound email and an idempotent per-message status store. Postal is a self-hosted mail server with an HTTP send API and signed webhooks. This package treats it as a typed delivery bus rather than a fire-and-forget mail driver: you send through a fully-typed client or transparently via MAIL_MAILER=postal, and you get the delivery outcome back as Laravel events instead of guessing. ### What It Covers - **Typed sending**: Send via the mail transport or a typed client, with retries, typed exceptions and correct handling of Postal returning HTTP 200 with an error envelope. - **Three connection types**: Per server: api (structured JSON), smtp-api (raw RFC 2822 over the HTTP API) and smtp (classic submission). - **Verified webhooks**: RSA signatures checked over the raw request body, deliveries deduplicated on Postal's webhook uuid, and one typed Laravel event per Postal event type. - **Delivery status store**: An idempotent row per message with latest status, open and click counters and the owning model, plus a deduplicated event log. - **Inbound email**: Postal routes deliver to a signed endpoint that becomes a typed event with bodies, headers, attachments and raw source. - **Multiple servers**: One config entry per Postal mail server addressed by name, or provisioned dynamically from a database via a registry contract. ## Diagnosis built in A postal:doctor command checks servers, keys, routes, store tables, the mailer and live connectivity with CI-friendly exit codes, so a misconfigured key or an unregistered webhook route surfaces before your users find it. postal:ping, postal:tail and postal:message cover the day-to-day: is it reachable, what is arriving, and what happened to this one message. > **Requirements**: PHP 8.4+, Laravel 12 or 13. Tested against Postal 3.x. This site sends its own mail through it. ## Installation ```bash composer require cboxdk/laravel-postal ``` ## Documentation Full documentation: [Postal integration for Laravel Docs](https://cbox.dk/packages/laravel-postal/docs/v0.md) ## Resources - [Postal documentation](https://docs.postalserver.io) --- --- title: "Risk Scoring for Laravel" description: "An explainable, config-driven request risk-scoring pipeline for Laravel. Weighted signals map to graduated outcomes: allow, flag, challenge, step-up, or reject, with the reasons behind every decision." github: "https://github.com/cboxdk/laravel-risk" packagist: "cboxdk/laravel-risk" language: "PHP" categories: - security - laravel url: "https://cbox.dk/packages/laravel-risk" docs: "https://cbox.dk/packages/laravel-risk/docs/v1.md" --- # Risk Scoring for Laravel An explainable, config-driven request risk-scoring pipeline for Laravel. Weighted signals map to graduated outcomes: allow, flag, challenge, step-up, or reject, with the reasons behind every decision. Risk Scoring for Laravel weights independent signals, IP reputation, disposable email, bot user-agents, honeypot and submit timing, into a single score, and maps that score to a graduated outcome: allow, flag, challenge, step-up, or reject. Every decision comes with the reasons behind it, so you can tune it, explain it, and defend it. ## The missing middle Most anti-abuse tools in the Laravel ecosystem are either a single-signal hard blocker (one IP list, one honeypot) or an opaque paid cloud whose verdicts you cannot explain. Neither fits an identity platform, where a wrong block locks a real user out and a regulator may ask why you made an automated decision. This package is the middle ground: many signals, a transparent weighted score, graduated friction instead of a binary block, and a full reasons breakdown on every assessment. Self-hosted, free-core, no data leaving your server. ### Free-Core Signals - **Honeypot & Timing**: Catches bots that fill hidden fields or submit a form in under two seconds, using the well-known spatie technique. Built in, no dependencies. - **User-Agent & Headers**: Flags curl, python-requests and headless clients, plus requests missing the Accept headers a real browser always sends. - **Disposable & MX**: A bundled throwaway-domain list catches disposable signup addresses, and an MX lookup catches domains that cannot receive mail, so the address is almost certainly fake. - **IP Reputation & Tor**: Addresses on many blocklists via stamparm/ipsum, refreshed with risk:refresh-ipsum, plus the official Tor exit-node list. Velocity is tracked HMAC-only in your cache. - **Opt-In Providers**: AbuseIPDB, Spamhaus DQS, Project Honey Pot, IPQualityScore, MaxMind GeoLite2 and HIBP plug in with your own key. An unconfigured signal contributes zero, never an error. - **Custom Signals**: Implement the Signal contract and add it to config. Return a SignalResult with points and a reason, or null. Impossible-travel, device fingerprints, your own heuristics. ## Explainable by construction The model is weighted-additive and deliberately not machine learning: the score is the sum of each signal points times its weight, mapped to the most severe outcome band it reaches. Defaults are all configurable: Flag at 15, Challenge at 30, Step-up at 60, Reject at 80. Allowlisted IPs and email domains bypass scoring entirely, and $assessment->reasons() tells you exactly which signals fired and why. > **Monitor first, enforce later**: The package ships in monitor mode: it scores and you log, but it does not act on the outcome yet. Calibrate the thresholds against your real traffic, then flip RISK_MODE=enforce. Anti-abuse tools that block on install cause outages; this one refuses to. > **Built for Article 22**: IP addresses and fingerprints are personal data, and GDPR Article 22 owes users explainability and human review over automated decisions. That is exactly why this scorer is transparent, not a black box: every assessment carries its reasons, with data-minimization defaults and retention guidance in the docs. ## Installation ```bash composer require cboxdk/laravel-risk ``` ## Documentation Full documentation: [Risk Scoring for Laravel Docs](https://cbox.dk/packages/laravel-risk/docs/v1.md) ## Resources - [GDPR Article 22: automated individual decision-making](https://gdpr-info.eu/art-22-gdpr/) - [stamparm/ipsum IP reputation feed](https://github.com/stamparm/ipsum) --- --- title: "SIEM for Laravel" description: "The SIEM log-streaming delivery engine for Laravel: a durable transactional outbox, queued batched delivery with retry and dead-letter, a per-stream circuit breaker, SSRF-guarded egress, encrypted secrets and per-field PII redaction." github: "https://github.com/cboxdk/laravel-siem" packagist: "cboxdk/laravel-siem" language: "PHP" categories: - security - observability - laravel url: "https://cbox.dk/packages/laravel-siem" docs: "https://cbox.dk/packages/laravel-siem/docs/v0.md" --- # SIEM for Laravel The SIEM log-streaming delivery engine for Laravel: a durable transactional outbox, queued batched delivery with retry and dead-letter, a per-stream circuit breaker, SSRF-guarded egress, encrypted secrets and per-field PII redaction. SIEM for Laravel is the delivery engine that ships normalized security events to Splunk HEC, Elastic ECS, Graylog GELF, ArcSight CEF, or any HTTP JSON collector. It is the Laravel wrapper over the framework-agnostic [SIEM Core](https://cbox.dk/packages/siem). The core owns the event model and the formatters, the shape of the data; this package owns delivery, the network, the durability and the secrets. It writes events to a transactional outbox in your own DB transaction, then a queued pump batches, redacts, formats and ships them off the request thread. ### Durable Delivery - **Transactional Outbox**: The outbox row commits inside your own DB transaction, so a rolled-back caller leaves no orphan event. A cheap insert on the request thread, delivery happens later. - **Queued & Batched**: All delivery is queued, so it never blocks the request. Triple-bounded batches by records, bytes and age keep each shipment sane. - **Retry & Dead-Letter**: Bounded exponential backoff with jitter and a hard retry cap into a dead-letter queue, plus a bounded outbox with an explicit backpressure policy. - **Per-Stream Circuit Breaker**: A failing destination trips its own circuit breaker instead of dragging down every other stream, so one broken SIEM does not stall the rest. - **Safe Egress**: SSRF-guarded and DNS-pinned HTTP, TLS verification always on, destination secrets encrypted at rest with the Laravel encrypter and scrubbed from logs. - **PII Redaction**: Per-field redaction (drop or hash) runs before formatting, so sensitive fields never reach the wire or a SIEM in the clear. > **What it guarantees**: Deny-by-default: no enabled stream, nothing delivered. At-least-once and unordered: duplicates are possible, so dedup by event id. Delivery never blocks the request, everything is bounded, and egress is SSRF-guarded with secrets encrypted at rest. ## Five destinations Register a destination and its endpoint is SSRF-checked and its secret encrypted. Splunk HEC ships NDJSON with a Splunk token, Elastic takes ECS JSON documents, Graylog uses GELF 1.1 over HTTP (never UDP), ArcSight takes CEF lines over HTTP, and a generic JSON destination hits any HTTP collector. An audit binding in Identity for Laravel consumes this layer to stream a tamper-evident audit trail. > **Testable end to end**: Compose InteractsWithLogStreams into your TestCase to run the whole pipeline in memory: fakeStreamSink() binds an in-memory sink, createLogStream() registers through the real registry, and pumpStream() runs a delivery cycle synchronously. ## Installation ```bash composer require cboxdk/laravel-siem ``` ## Documentation Full documentation: [SIEM for Laravel Docs](https://cbox.dk/packages/laravel-siem/docs/v0.md) ## Resources - [Transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html) - [Circuit breaker pattern](https://martinfowler.com/bliki/CircuitBreaker.html) --- --- title: "Tax for Laravel" description: "A self-hostable consumption-tax engine for Laravel. It owns the calculation logic, place-of-supply, reverse-charge, rate application, inclusive and exclusive, and sources only the rate data behind a pluggable contract. No forced third-party tax SaaS." github: "https://github.com/cboxdk/laravel-tax" packagist: "cboxdk/laravel-tax" language: "PHP" categories: - billing - laravel - php url: "https://cbox.dk/packages/laravel-tax" docs: "https://cbox.dk/packages/laravel-tax/docs/v0.md" --- # Tax for Laravel A self-hostable consumption-tax engine for Laravel. It owns the calculation logic, place-of-supply, reverse-charge, rate application, inclusive and exclusive, and sources only the rate data behind a pluggable contract. No forced third-party tax SaaS. Tax for Laravel owns the tax calculation logic, place-of-supply, reverse-charge, rate application, inclusive and exclusive pricing, and sources only the rate data behind a pluggable contract. There is no forced third-party calculation SaaS. It is built on [Geo for Laravel](https://cbox.dk/packages/laravel-geo), so every supply is assessed against a jurisdiction resolved from canonical ISO data. Tax is a function of seller registrations, buyer jurisdiction and product type, never a fuzzy country-name match. ## Own the logic, source the data The engine decides whether and how to tax a supply. The TaxRateSource contract supplies the rate number, whether that is an EU TEDB feed, the us-tax-data dataset or a commercial adapter. A missing rate is refused, never assumed to be zero percent. That boundary is the whole design: the parts that must be correct and auditable live in the package, and the data that changes constantly is versioned separately and fetched at runtime, so a rate change is a dataset release rather than a package upgrade. Pin it at a tagged release for a deterministic build. ### What It Does - **EU VAT**: Article 44, 45 and 58 place-of-supply, intra-EU B2B reverse charge, VIES-validated customer tax IDs, and the Article 59c 10,000 euro micro-business threshold: a below-threshold, non-opted seller charges origin VAT, an opted-in or over-threshold seller charges destination. - **National VAT and GST**: UK, CH, NO, AU, NZ, MX, SG, TW, UAE, SA, BH, OM, TR, CL, ID, VN, PH, JP, KR, TH and UA, plus India dual GST (IGST versus CGST and SGST) and Malaysia SST. - **US Sales Tax**: Three gates before a rate applies: the state must resolve, the seller must have nexus in it, and the product must be taxable there, otherwise it returns NotRegistered or Exempt rather than a wrong charge. Rates, 25-category taxability, Wayfair nexus thresholds and intrastate sourcing come from the us-tax-data dataset across all 51 jurisdictions, with 41 states carrying local rate records from official state files. - **Canada GST and HST**: Province-level combined rate with cross-border B2B self-assessment, resolved at province level with no local tax lookup. - **Multi-Entity Routing**: Tax depends on which selling entity issues the invoice. SellerRegistrations is the seller side the billing engine supplies per invoice, so the same buyer is taxed correctly whichever entity sells. - **Exact Money**: Amounts are brick/money and rate maths rounds half-up once. Deny-by-default throughout: no regime or no rate raises an exception, never a silent zero. > **Honest coverage**: The docs carry the full per-country table with sources and confidence, plus an honest list of jurisdictions omitted until their rate data is verified, a national-VAT batch pending primary-source confirmation, some Pakistani provinces and Brazil. The engine omits rather than ship a rate it cannot stand behind. Nexus is asserted here from an explicit seller registration, never inferred from one invoice, because economic nexus turns on cumulative sales a single supply cannot carry. Working out where a registration is owed in the first place is the job of [Economic Nexus for Laravel](https://cbox.dk/packages/laravel-nexus), which accumulates activity per state and reports where a threshold has been crossed. ## Buyer exemptions A query can carry a resale, nonprofit or government certificate. Applied deny-by-default over the regime verdict, a valid exemption covering the taxed jurisdiction rewrites a standard line to Exempt, keeping net, zeroing tax, and recording the certificate reference on the assessment. Reverse-charge, not-registered and zero-rated outcomes are left alone, and an expired certificate or one for a different jurisdiction does not exempt. The engine computes the assessment. Capturing and verifying certificates stays your concern. ## Contracts all the way down TaxCalculator, TaxRegime, TaxRateSource, RegimeRegistry, AddressGeocoder, VatIdValidator and ReturnAggregator are all contracts you can bind and override. Rate sources compose: static, remote, caching and chain, and they resolve by taxability category so reduced and zero bands apply when a bound source supplies them. None are fabricated by default. Unmodelled jurisdictions and missing rates are refused, not guessed. The shipped GeocodioGeocoder resolves US and Canada addresses to state and province. Only the geocoding is taken from Geocodio: the rate and the calculation stay in the engine, and Geocodio's own tax append is not used. Turning on the opt-in rooftop option makes the adapter attach a county FIPS as a locality code, and the rate source then stacks the matched local record onto the state share per that state's rate basis, returning an all-in rate at authoritative confidence. A county code cannot single out a specific address's city or special-district record, so where none matches the engine falls back to the state rate and labels it derived rather than passing it off as all-in. Closing that last gap is a point-to-jurisdiction crosswalk, which is a data question: the stacking is already wired end to end. ## Installation ```bash composer require cboxdk/laravel-tax ``` ## Documentation Full documentation: [Tax for Laravel Docs](https://cbox.dk/packages/laravel-tax/docs/v0.md) ## Resources - [EU VAT place-of-supply rules](https://taxation-customs.ec.europa.eu/vat-place-supply-services_en) - [brick/money](https://github.com/brick/money) --- --- title: "System Metrics" description: "Real-time system metrics from Linux and macOS in pure PHP. CPU, memory, storage, network, and container metrics with no extensions or dependencies." github: "https://github.com/cboxdk/system-metrics" packagist: "cboxdk/system-metrics" language: "PHP" categories: - php - observability url: "https://cbox.dk/packages/system-metrics" docs: "https://cbox.dk/packages/system-metrics/docs/v3.md" --- # System Metrics Real-time system metrics from Linux and macOS in pure PHP. CPU, memory, storage, network, and container metrics with no extensions or dependencies. System Metrics reads CPU, memory, storage, network, and process data directly from /proc and /sys on Linux, and from sysctl/vm_stat on macOS. No PHP extensions, no Composer dependencies, no C bindings. One require and it works everywhere PHP runs. ### Pure PHP, Full Visibility - **Container Aware**: Reads cgroup v1 and v2 limits instead of host resources. Reports 85% memory usage (of container limit) instead of the misleading 0.7% (of host RAM). - **Zero Dependencies**: No ext-posix, no ext-sysinfo, no Composer packages. Pure PHP 8.3 with readonly classes and immutable DTOs. - **CPU Metrics**: Raw counters, per-core data, and usage percentage via delta snapshots. Works in containers with CPU limit awareness. - **Memory Metrics**: Physical RAM, swap, buffers, cache, and available memory. Container-aware with cgroup limit detection. - **Result Pattern**: Every operation returns Result for explicit error handling. No exceptions for expected failures like missing /proc files. - **PHPStan Level 9**: Maximum static analysis coverage with 89.9% test coverage. Type-safe from the ground up. ## Why this matters in containers When your container has 512MB memory but the host has 64GB, standard PHP functions like memory_get_usage() show the PHP process memory, not the container usage. And /proc/meminfo shows the host. System Metrics reads the cgroup memory limit and current usage, giving you the real picture of your container resource consumption. > **Not a monitoring agent**: System Metrics is a library, not a daemon. It reads metrics on demand when your code asks for them. For continuous monitoring, pair it with Queue Metrics for Laravel or your own collection loop. ## Installation ```bash composer require cboxdk/system-metrics ``` ## Documentation Full documentation: [System Metrics Docs](https://cbox.dk/packages/system-metrics/docs/v3.md) ## Resources - [Linux cgroup v2 documentation](https://docs.kernel.org/admin-guide/cgroup-v2.html) --- --- title: "DNS Toolkit" description: "A zero-runtime-dependency DNS toolkit for PHP. Speaks the wire protocol over raw sockets to read answers straight from a zone authoritative nameservers, plus domain verification, DNSSEC validation and intoDNS-style diagnostics." github: "https://github.com/cboxdk/dns" packagist: "cboxdk/dns" language: "PHP" categories: - php - infrastructure - security url: "https://cbox.dk/packages/dns" docs: "https://cbox.dk/packages/dns/docs/v1.md" --- # DNS Toolkit A zero-runtime-dependency DNS toolkit for PHP. Speaks the wire protocol over raw sockets to read answers straight from a zone authoritative nameservers, plus domain verification, DNSSEC validation and intoDNS-style diagnostics. DNS Toolkit speaks the DNS wire protocol over raw sockets, so it can read a zone answer straight from the zone own authoritative nameservers rather than from whatever a recursive resolver happens to have cached. That distinction is the point: it is what makes domain-ownership verification and propagation checks trustworthy. On top of the resolver it ships domain verification, an authoritative-versus-recursive propagation check, a full DNSSEC chain validator, and an intoDNS-style diagnostics engine, all framework-agnostic and driven through one contract you can fake in tests. ### What It Does - **Authoritative Resolver**: Discovers a zone nameservers and queries them directly with recursion disabled, so a record you read is really published right now, at the source of truth, not a cached copy. - **Domain Verification**: Reads the ownership TXT token against the authoritative servers, so a just-published record is visible immediately instead of waiting out a recursive resolver negative cache. - **DNSSEC Validation**: Walks the chain itself, root to TLD to zone, checking every signature rather than trusting a resolver AD bit. Signature math is delegated to OpenSSL and libsodium, never hand-rolled. - **Diagnostics Engine**: intoDNS and MxToolbox-style health checks, delegation traces and propagation comparisons, grouped into findings you can act on. - **Zero Runtime Dependencies**: Runs on ext-sockets and the standard library. Nothing to audit downstream, nothing to keep patched, no dig binary shelled out to. - **Contract-Driven**: Every capability sits behind one resolver contract you can bind, fake or replace, so tests run without touching the network. > **Right tool for verification, wrong tool for a hot path**: Every lookup is a fresh network round-trip with no cache. That is deliberate: what it returns is the ground truth right now. It also means a lookup is materially slower than your OS resolver. Reach for it when freshness or transparency matters more than throughput. Do not drop it in as a general-purpose resolver on a hot path, a cached system resolver is faster there. ## Honest about DNSSEC There is no vetted, maintained PHP library to wrap for DNSSEC chain validation, so this package does the protocol work itself: canonical form, key and signature encoding, validity windows, NSEC and NSEC3 denial proofs. Only the signature math is delegated, to OpenSSL for RSA and ECDSA and libsodium for Ed25519. The module is built against real captured signed-zone vectors and was adversarially reviewed, where a cross-zone forgery bypass was found and fixed before release. ## Installation ```bash composer require cboxdk/dns ``` ## Documentation Full documentation: [DNS Toolkit Docs](https://cbox.dk/packages/dns/docs/v1.md) ## Resources - [DNSSEC (RFC 4033-4035)](https://www.rfc-editor.org/rfc/rfc4033) - [intoDNS diagnostics](https://intodns.com/) --- --- title: "DNS for Laravel" description: "The DNS toolkit for Laravel: authoritative lookups, domain-ownership verification, DNSSEC validation and intoDNS-style diagnostics, exposed through a facade, Artisan commands and validation rules." github: "https://github.com/cboxdk/laravel-dns" packagist: "cboxdk/laravel-dns" language: "PHP" categories: - laravel - infrastructure - security url: "https://cbox.dk/packages/laravel-dns" docs: "https://cbox.dk/packages/laravel-dns/docs/v1.md" --- # DNS for Laravel The DNS toolkit for Laravel: authoritative lookups, domain-ownership verification, DNSSEC validation and intoDNS-style diagnostics, exposed through a facade, Artisan commands and validation rules. DNS for Laravel is the framework integration for the [DNS Toolkit](https://cbox.dk/packages/dns) engine. The protocol handling, verification, propagation, SPF and DMARC parsing, and DNSSEC chain validation all live in that core library. This package wires it into the container, config, the console and the validator, so it is one composer require away in a Laravel app. It works with zero configuration out of the box against a raw UDP and TCP socket resolver. ### Wired Into Laravel - **Facade**: Dns::lookup(), verifyDomain(), diagnose() and dnssec()->validate() from a single facade, or inject the core Cbox\Dns\Dns and Resolver contract straight from the container. - **Artisan Commands**: dns:lookup, dns:verify, dns:diagnose, dns:propagation and dns:dnssec, each returning a table or grouped findings with a non-zero exit on errors for CI. - **Validation Rules**: DnsRecordExists and DomainVerified plug straight into the validator, so an MX record or an ownership token is checked at the request boundary. - **Socket or DoH Transport**: A raw UDP and TCP socket resolver for authoritative reads, or a DNS-over-HTTPS JSON transport when you only need recursive answers. Switch with one config key. - **Zero Config**: Auto-discovered service provider and a working resolver against 1.1.1.1 out of the box. Publish config/dns.php only when you want to tune the transport, nameserver or timeout. - **Authoritative Reads**: The socket transport can target a zone own authoritative nameservers, which domain-ownership verification and propagation checks require, behind an SSRF filter that only lifts for local testing. > **Socket versus DoH**: The socket transport can query a zone authoritative nameservers directly, which ownership verification and propagation checks need. The DoH transport only queries a provider recursive resolver, so it cannot answer authoritative or propagation queries. Pick socket when freshness matters, DoH when a plain recursive lookup is enough. ## Installation ```bash composer require cboxdk/laravel-dns ``` ## Documentation Full documentation: [DNS for Laravel Docs](https://cbox.dk/packages/laravel-dns/docs/v1.md) ## Resources - [DNS-over-HTTPS (RFC 8484)](https://www.rfc-editor.org/rfc/rfc8484) --- --- title: "FPM Exporter" description: "Prometheus exporter for PHP-FPM. Collects metrics from FPM pools, opcache, and Laravel queues via FastCGI. Multi-site support." github: "https://github.com/cboxdk/fpm-exporter" language: "Go" categories: - infrastructure - observability url: "https://cbox.dk/packages/fpm-exporter" docs: "https://cbox.dk/packages/fpm-exporter/docs/v3.md" --- # FPM Exporter Prometheus exporter for PHP-FPM. Collects metrics from FPM pools, opcache, and Laravel queues via FastCGI. Multi-site support. FPM Exporter is a lightweight Prometheus exporter that collects metrics from PHP-FPM pools, OPcache, and Laravel applications. It communicates with PHP-FPM directly via the FastCGI protocol, which means it works with any deployment: Docker, Kubernetes, VMs, or bare metal. ### Complete PHP Observability - **Pool Metrics**: Active/idle processes, listen queue depth, max children reached, slow requests, accepted connections. Everything you need to tune pm.max_children. - **Per-Pool OPcache**: Hit rate, memory usage, wasted memory percentage, and restart counts for each FPM pool individually. Most tools only show system-wide OPcache stats. - **Automatic Pool Discovery**: Discovers all PHP-FPM pools automatically via php-fpm -tt. No manual configuration when pools are added or removed. - **Laravel Integration**: Queue sizes per connection, application info, cache driver state, and configuration details. Designed for the Queue Autoscale for Laravel feedback loop. - **Multi-Site Support**: Monitor multiple Laravel applications from a single exporter instance. Each site gets its own metric labels. - **Zero Dependencies**: Single static binary. Download, configure, run. Available as a Docker image, systemd service, or standalone binary. ## How it works FPM Exporter sends FastCGI requests directly to each PHP-FPM pool using the fcgx library. For pool status, it reads the built-in status page. For OPcache, it injects a small PHP script via FastCGI that calls opcache_get_status() and returns JSON. This per-pool approach is unique because OPcache is shared across workers in a pool but isolated between pools. > **Pre-built dashboards**: FPM Exporter ships with ready-to-import Grafana dashboards and Prometheus alerting rules. Install and start monitoring in minutes. ## Documentation Full documentation: [FPM Exporter Docs](https://cbox.dk/packages/fpm-exporter/docs/v3.md) ## Resources - [Prometheus Best Practices for PHP](https://prometheus.io/docs/practices/naming/) --- --- title: "Queue Monitor for Laravel" description: "Queue visibility without Horizon or Redis. Track every job on any driver with per-job CPU, memory, retry chains, replay, and a terminal dashboard." github: "https://github.com/cboxdk/laravel-queue-monitor" packagist: "cboxdk/laravel-queue-monitor" language: "PHP" categories: - laravel - observability url: "https://cbox.dk/packages/laravel-queue-monitor" docs: "https://cbox.dk/packages/laravel-queue-monitor/docs/v1.md" --- # Queue Monitor for Laravel Queue visibility without Horizon or Redis. Track every job on any driver with per-job CPU, memory, retry chains, replay, and a terminal dashboard. Queue Monitor for Laravel tracks every queue job across any driver: database, Redis, SQS, Beanstalkd, or custom. Full lifecycle tracking from queued to completed, with retry chains, exception traces, and resource metrics. Built on top of [Queue Metrics for Laravel](https://cbox.dk/packages/laravel-queue-metrics/docs) for CPU and memory instrumentation. ### Job-Level Visibility - **Individual Job Tracking**: Every job gets a UUID. Track its full lifecycle: queued, processing, completed, failed, timeout, or cancelled. See retry chains, exception traces, and which server processed it. - **Job Replay**: Full payload serialization lets you re-dispatch failed jobs from stored payloads via API, CLI, or programmatically. - **Resource Metrics**: Per-job CPU time, peak memory usage, worker memory limits, and file descriptor counts for every execution. - **Terminal Dashboard**: A k9s-style TUI with keyboard navigation. Filter by status, queue, or search. Drill into job details over SSH. - **REST API**: 14 endpoints for listing, filtering, replaying, and analyzing jobs programmatically. Paginated, filterable, with automatic payload redaction. - **Analytics**: Per-queue, per-server, and per-job-class breakdowns with p50/p95/p99 percentiles. Health scoring, SLA compliance, and failure pattern analysis. ## Any queue driver Most queue monitoring tools only work with Redis and Horizon. Queue Monitor hooks into Laravel's native queue events, so it works with every driver. Optional integrations with Horizon and [Queue Autoscale for Laravel](https://cbox.dk/packages/laravel-queue-autoscale/docs) add supervisor data, scaling timelines, and SLA tracking when available. > **Zero configuration**: Install, publish config, run migrations. The package auto-registers listeners on Laravel's queue events and starts monitoring immediately. Works alongside Horizon or standalone. ## Installation ```bash composer require cboxdk/laravel-queue-monitor ``` ## Documentation Full documentation: [Queue Monitor for Laravel Docs](https://cbox.dk/packages/laravel-queue-monitor/docs/v1.md) --- --- title: "Cbox Init" description: "PID 1 process manager for Docker containers. Handles signal forwarding, zombie reaping, PHP-FPM auto-tuning, health checks, and Prometheus metrics." github: "https://github.com/cboxdk/init" language: "Go" categories: - infrastructure - php url: "https://cbox.dk/packages/init" docs: "https://cbox.dk/packages/init/docs/v3.md" --- # Cbox Init PID 1 process manager for Docker containers. Handles signal forwarding, zombie reaping, PHP-FPM auto-tuning, health checks, and Prometheus metrics. ## The problem with PHP in containers Most PHP Docker setups use supervisord or a shell script as the entrypoint. Neither is a proper PID 1. Zombie processes accumulate, signals get lost, and health checks are an afterthought. Cbox Init is a process manager built specifically for PHP in containers. It handles signal forwarding, zombie reaping, multi-process orchestration, and PHP-FPM auto-tuning in a single static binary. No runtime dependencies, no configuration files to maintain for simple setups. ### Built for PHP - **PID 1 Process Manager**: Proper signal handling (SIGTERM, SIGINT, SIGQUIT), zombie reaping, and clean process tree management. No orphaned workers. - **PHP-FPM Auto-Tuning**: Reads cgroup memory and CPU limits, applies application profiles (dev, light, medium, heavy, bursty), and calculates optimal worker counts automatically. - **Multi-Process Orchestration**: DAG-based dependency management with priority-based startup. Run PHP-FPM, Nginx, queue workers, cron, and schedulers in a single container. - **Health Monitoring**: TCP, HTTP, and exec-based health checks with configurable success thresholds. File-based Kubernetes readiness probes. - **Scheduled Tasks**: Built-in cron scheduler with per-task statistics. Heartbeat monitoring integration with Healthchecks.io, Cronitor, and Better Uptime. - **Framework Scaffolding**: Generate configs for Laravel, Symfony, WordPress, Magento, Drupal, Next.js, Nuxt, and Node.js with a single command. ## Auto-tuning for real workloads Instead of guessing pm.max_children, Cbox Init reads your container resource limits and applies application profiles. A "heavy" Laravel app with 512MB gets different settings than a "light" API service with 2GB. The calculator considers memory per worker, CPU cores, and safety margins. > **Quick start**: Add Cbox Init to any PHP Docker image with a single COPY instruction: COPY --from=cboxdk/init:latest /usr/local/bin/init /usr/local/bin/init. Then use init as your ENTRYPOINT. ## Observability built in Every process exposes Prometheus metrics, and the REST API gives you runtime control over scaling, process restarts, and configuration changes. The terminal UI (TUI) provides a k9s-style interface for interactive management during debugging. ## Documentation Full documentation: [Cbox Init Docs](https://cbox.dk/packages/init/docs/v3.md) --- --- title: "Queue Metrics for Laravel" description: "Queue monitoring and metrics for Laravel. Job execution times, success rates, failure patterns, worker health, and Prometheus export." github: "https://github.com/cboxdk/laravel-queue-metrics" packagist: "cboxdk/laravel-queue-metrics" language: "PHP" categories: - laravel - observability url: "https://cbox.dk/packages/laravel-queue-metrics" docs: "https://cbox.dk/packages/laravel-queue-metrics/docs/v3.md" --- # Queue Metrics for Laravel Queue monitoring and metrics for Laravel. Job execution times, success rates, failure patterns, worker health, and Prometheus export. Queue Metrics for Laravel collects detailed telemetry from your queue workers: job execution times, success rates, failure patterns, throughput, and worker health. It is the metrics foundation for Queue Autoscale for Laravel, but also valuable as a standalone monitoring solution. ### Queue Visibility - **Real-Time Metrics**: Execution times, success rates, failure patterns, and throughput for every queue and job class. Updated in real-time with ~1-2ms overhead per job. - **Health Scores**: A 0-100 health score for each queue based on failure rate, processing time trends, and queue depth. Instant signal for operational dashboards. - **Prometheus Export**: Built-in /metrics endpoint for Prometheus scraping. Pre-configured metric names following Prometheus naming conventions. - **Worker Heartbeats**: Detect stale or crashed workers automatically. Each worker sends periodic heartbeats; missing heartbeats trigger alerts. - **Event System**: Listen for MetricsRecorded, HealthScoreChanged, WorkerEfficiencyChanged, and QueueDepthThresholdExceeded events in your application. - **Multi-Tenancy Hooks**: Enrich metrics with custom metadata via the hook system. Tag metrics by tenant, team, or any other dimension. ## Storage backends Redis is recommended for performance-critical environments. Database storage is available for teams that prefer persistence and queryability over raw speed. Both backends support the same API surface. > **Zero configuration**: Install with composer require, and metrics collection starts automatically. The package auto-registers its service provider and begins collecting from the first job dispatch. ## Installation ```bash composer require cboxdk/laravel-queue-metrics ``` ## Documentation Full documentation: [Queue Metrics for Laravel Docs](https://cbox.dk/packages/laravel-queue-metrics/docs/v3.md) --- --- title: "Queue Autoscale for Laravel" description: "SLA-driven autoscaling for Laravel queue workers. Declare how long a job may wait before pickup, and the manager solves for the worker count each cycle using queueing theory, bounded by the CPU and memory the host actually has." github: "https://github.com/cboxdk/laravel-queue-autoscale" packagist: "cboxdk/laravel-queue-autoscale" language: "PHP" categories: - laravel - infrastructure url: "https://cbox.dk/packages/laravel-queue-autoscale" docs: "https://cbox.dk/packages/laravel-queue-autoscale/docs/v4.md" --- # Queue Autoscale for Laravel SLA-driven autoscaling for Laravel queue workers. Declare how long a job may wait before pickup, and the manager solves for the worker count each cycle using queueing theory, bounded by the CPU and memory the host actually has. ## SLA-driven, not threshold-driven Most autoscalers react to queue depth thresholds. When the queue grows past 100 jobs, add workers. When it drops below 10, remove them. This is backwards. You do not care about queue depth. You care about how long jobs wait before a worker picks them up. Queue Autoscale for Laravel lets you define SLA targets: "jobs on the default queue should start processing within 30 seconds." Each evaluation cycle the manager solves for the worker count that holds that target, then constrains it by the CPU and memory measured on the host. Worker counts are derived, not configured. ### How It Scales - **Little's Law**: The steady-state baseline is workers = arrival rate × average job time. The arrival rate is only used when the estimator is confident enough; otherwise the observed processing rate stands in, and retry volume is subtracted so retries are not counted as new arrivals. - **Backlog Drain**: A second candidate kicks in once half the SLA budget is consumed, with a quadratic urgency curve: 1.0x at half the budget, 3.0x at the target, capped at 5.0x. The larger of the two candidates wins. - **p95 Pickup Time**: The SLA signal is the p95 of real observed pickup times over a sliding window, falling back to oldest-job age when there are too few samples. Measured spawn latency is subtracted from the budget, so the manager reacts to when a worker will actually be running. - **Failure Fuse**: A downstream outage looks exactly like load to any autoscaler. The fuse detects a high failure rate, holds the queue at workers.min instead of pouring workers into a wall, then probes with a single worker before releasing. - **Resource Awareness**: CPU and memory ceilings measured on the host constrain every decision, reduced by what other queues on the same host already use. It will not scale beyond what the machine can carry. - **Cluster & Worker Groups**: Managers auto-join via Redis, elect a leader and distribute worker targets across hosts. A worker group is one worker set polling several queues in strict priority order. ## How the algorithm works The default hybrid strategy computes two candidate worker counts and takes the larger. Little's Law gives the steady-state baseline; backlog drain protects the SLA once half the budget is gone. A saturation guard adds a worker when the existing ones are above 90% utilisation but neither calculation asked for more. The target is then clamped to the queue's min and max, smoothed by hysteresis that limits scale-down to one worker per cycle, and blocked by an anti-flapping cooldown that stops only reversals, never further scaling in the same direction. > **See what it is thinking**: php artisan queue:autoscale:debug --queue=payments prints one queue's raw state, metrics and fuse status. queue:autoscale:cluster shows the leader, hosts, capacity and workload targets. Use them to tune SLA targets against real numbers. ## Installation ```bash composer require cboxdk/laravel-queue-autoscale ``` ## Documentation Full documentation: [Queue Autoscale for Laravel Docs](https://cbox.dk/packages/laravel-queue-autoscale/docs/v4.md) ## Resources - [Little's Law on Wikipedia](https://en.wikipedia.org/wiki/Little%27s_law) --- --- title: "Statamic MCP" description: "MCP server for Statamic CMS. Lets AI assistants read blueprints, query entries, manage collections, and understand your content model. 11 domain routers." github: "https://github.com/cboxdk/statamic-mcp" packagist: "cboxdk/statamic-mcp" language: "PHP" categories: - statamic url: "https://cbox.dk/packages/statamic-mcp" docs: "https://cbox.dk/packages/statamic-mcp/docs/v3.md" --- # Statamic MCP MCP server for Statamic CMS. Lets AI assistants read blueprints, query entries, manage collections, and understand your content model. 11 domain routers. Statamic MCP connects your Statamic CMS to AI assistants like Claude, Cursor, ChatGPT, and Windsurf via the Model Context Protocol. Your AI assistant can read blueprints, query entries, manage collections, and understand your content model directly. ### AI-Powered CMS Development - **11 Router-Based Tools**: Instead of 140+ individual tools, v2 uses a router architecture that groups operations by domain. Fewer tools means better AI reasoning and more accurate results. - **Scoped API Tokens**: 21 granular permission scopes in domain:permission format. Give read-only access to blueprints, full access to entries, or any combination. SHA-256 hashed storage. - **Control Panel Dashboard**: Vue 3 dashboard with Connect, Tokens, Activity, and Settings tabs. Generate AI client configurations with one click. - **Audit Logging**: Every MCP operation is logged with timestamps, token identity, and operation details. Full visibility into what AI assistants are doing with your content. - **Rate Limiting**: Per-token rate limits prevent runaway AI loops from overwhelming your site. Configurable per token and globally. - **Multi-Client Support**: Copy-paste configuration generation for Claude Desktop, Cursor, ChatGPT, and Windsurf. Both CLI (stdio) and web (HTTP) transports. ## The router architecture Version 1 exposed every Statamic operation as its own MCP tool: create_entry, update_entry, delete_entry, list_entries, get_entry, and so on for every resource type. That gave LLMs 140+ tools to choose from, which degraded their reasoning. Version 2 consolidates these into 11 domain routers (entries, blueprints, terms, globals, etc.) where the AI specifies the action as a parameter. > **Getting started**: Install with composer require cboxdk/statamic-mcp, then run php artisan mcp:statamic:install. The CP dashboard guides you through token creation and AI client configuration. ## Installation ```bash composer require cboxdk/statamic-mcp ``` ## Documentation Full documentation: [Statamic MCP Docs](https://cbox.dk/packages/statamic-mcp/docs/v3.md) ## Resources - [Model Context Protocol Specification](https://modelcontextprotocol.io/) - [MCP Tools Documentation](https://modelcontextprotocol.io/docs/concepts/tools) --- --- title: "SSRF Guard for Laravel" description: "A hardened, config-driven guard against server-side request forgery (SSRF) for outbound URLs in Laravel. Validates any URL your app is about to fetch and refuses private, reserved, or cloud-metadata addresses." github: "https://github.com/cboxdk/laravel-ssrf" packagist: "cboxdk/laravel-ssrf" language: "PHP" categories: - security - laravel - php url: "https://cbox.dk/packages/laravel-ssrf" docs: "https://cbox.dk/packages/laravel-ssrf/docs/v1.md" --- # SSRF Guard for Laravel A hardened, config-driven guard against server-side request forgery (SSRF) for outbound URLs in Laravel. Validates any URL your app is about to fetch and refuses private, reserved, or cloud-metadata addresses. SSRF Guard for Laravel validates any URL your application is about to fetch, a webhook endpoint, an avatar or import URL, an OAuth callback, and refuses anything that points at a private, reserved, or cloud-metadata address. It resolves DNS once, pins the connection to those exact IPs, and will not follow redirects, then behaves like the normal HTTP client. > **One line to guard a request**: Http::ssrf()->post($url, $payload) validates and pins at send time, so the URL that is checked is the URL that is fetched. It refuses redirects, otherwise behaves like the normal Laravel HTTP client, and throws Cbox\Ssrf\Exceptions\BlockedUrl when the target is unsafe. One client can serve a loop of endpoints, each guarded on its own. ## Why a correct SSRF guard is hard An attacker who controls a URL your server fetches can reach your internal network: 169.254.169.254 for cloud credentials, 127.0.0.1:6379 for Redis, 10.0.0.5/admin for an internal panel. Most home-grown checks miss DNS rebinding, redirect hops, or IPv6 transition forms. This package handles all three, and is honest about the one thing application code cannot fully solve. ### What It Defends Against - **DNS Rebinding (TOCTOU)**: Resolves the host once and pins the connection to that exact IP via CURLOPT_RESOLVE, keeping TLS and SNI intact, with a post-connection check that the socket actually connected to a validated address. - **Redirect-Based Bypass**: Refuses to follow redirects on guarded requests, so a 30x response to an internal host cannot smuggle the request onward. - **Alternate IP Encodings**: Decimal (2130706433) and hex (0x7f000001) IPv4 literals are normalized before validation, closing the browser-redirect encoding tricks. - **IPv6 Transition Forms**: IPv4-mapped, IPv4-compatible, 6to4, NAT64 and Teredo addresses are blocked wholesale, and their embedded IPv4 is extracted and re-checked. This is the Symfony CVE-2026-48736 class of bypass. - **Cloud Metadata**: AWS, GCP and Azure 169.254.169.254, AWS IMDSv2 IPv6 fd00:ec2::254, Alibaba, Oracle, and the metadata.google.internal name are all refused. - **Reserved Ranges & URL Tricks**: Loopback, RFC 1918, link-local, ULA, CGNAT, TEST-NET and multicast for both IPv4 and IPv6, plus embedded credentials, blocked host suffixes, and non-http(s) schemes. ## Three ways to use it Guard an outbound request with Http::ssrf(). Validate user-supplied URLs at the form boundary with the PublicUrl validation rule. Or resolve the UrlGuard contract directly and call assertSafe(), isSafe() or pinnedOptions() to guard a request from your own HTTP client. Browser-redirect targets like OAuth authorize URLs are validated without a DNS lookup via assertSafeRedirect(), since the browser resolves them. The full policy lives in config/ssrf.php: allowed_schemes, allow_credentials, blocked_hosts, blocked_host_suffixes, blocked_ips, blocked_cidrs, pin_dns, and a master enforce switch. A single-tenant or on-prem install that must reach internal hosts can set SSRF_ENFORCE=false, and the scheme, credential, and blocked-host checks still run. One guard can serve several outbound sinks with different rules. Pass a scheme allow-list, or permit embedded credentials, for a single call: a webhook sink stays HTTPS-only while a git sink accepts ssh and a deploy token in the URL. An override never relaxes the IP and host enforcement, and the same options work on the PublicUrl rule per field. > **Defense in depth, not a complete fix**: A network egress allow-list is the real answer: a firewall, security-group or egress proxy that can only reach approved destinations closes SSRF completely. Use this guard and restrict egress, block 169.254.169.254 at the network, and require IMDSv2. An HTTP proxy defeats connection pinning, and response content is out of scope. ## Deterministic in tests Compose the InteractsWithSsrf trait into your base TestCase and DNS becomes a fixture: fakeSsrfDns() maps a hostname to whatever address you want to assert against, withSsrfConfig() changes the policy, and ssrfGuard() hands you the guard rebuilt from both. That last part matters, because the guard and its policy are container singletons. Change config after something has resolved the guard and your test asserts against the old policy. Every helper drops both singletons first, so you can prove your guard blocks evil.test resolving to 169.254.169.254 without touching the network. ## Installation ```bash composer require cboxdk/laravel-ssrf ``` ## Documentation Full documentation: [SSRF Guard for Laravel Docs](https://cbox.dk/packages/laravel-ssrf/docs/v1.md) ## Resources - [OWASP: Server-Side Request Forgery](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery) - [CVE-2026-48736: IpUtils IPv6 transition-form SSRF bypass](https://symfony.com/blog/cve-2026-48736-iputils-private-subnets-omits-ipv6-transition-forms-ssrf-bypass-in-noprivatenetworkhttpclient) --- --- title: "Telemetry for Laravel" description: "Collector-free telemetry for Laravel. Prometheus metrics, OTLP traces, events and logs. No C extension, no protobuf, no sidecar. Metrics that work under FPM." github: "https://github.com/cboxdk/laravel-telemetry" packagist: "cboxdk/laravel-telemetry" language: "PHP" categories: - laravel - observability url: "https://cbox.dk/packages/laravel-telemetry" docs: "https://cbox.dk/packages/laravel-telemetry/docs/v2.md" --- # Telemetry for Laravel Collector-free telemetry for Laravel. Prometheus metrics, OTLP traces, events and logs. No C extension, no protobuf, no sidecar. Metrics that work under FPM. Telemetry for Laravel gives you Prometheus metrics, OTLP traces, events and trace-correlated logs from a single facade. No collector, no C extension, no protobuf. Metrics aggregate in Redis or APCu, so web workers, queue workers and nodes all write to the same series. Prometheus scrapes a route, and traces go straight to any OTLP backend as plain HTTP JSON. ### Every Signal, One Package - **Prometheus Endpoint**: Scrape endpoints with IP allowlisting and per-endpoint metric filters. Expose a narrow public endpoint and a full internal one from the same app. - **OTLP Without the SDK**: Exports traces, metrics and logs as spec-stable OTLP HTTP JSON to Tempo, Grafana Cloud, Honeycomb or any other OTLP backend. - **Auto-Instrumentation**: Requests, queue jobs, DB queries, transactions, scheduled tasks, mail, notifications and outgoing HTTP calls. Full W3C trace propagation from dispatch site into queue workers. - **Trace-Correlated Logs**: A telemetry log channel turns Laravel logs into OTLP log records with severity mapping and trace ids, so a log line links to its trace waterfall. - **Testing Fakes**: Telemetry::fake() with assertions for counters, gauges, histograms, spans and events. Test your instrumentation like you test your mail. - **Grafana Dashboards**: Ships four dashboards via telemetry:dashboards: overview, requests, queue and schedule, and drill-down. Service-scoped and ready for the LGTM stack. ## Why metrics fail under FPM PHP is shared-nothing: in-process metric state dies with the request. That is why the official OpenTelemetry SDK's metrics need a collector sidecar to work under FPM. This package sidesteps the problem by aggregating metrics in shared storage instead. A counter incremented in a web request, a queue worker and an Artisan command ends up as one correct series, whatever node it ran on. Resource attribution is built in: peak memory and CPU time per request, job and scheduled task, plus per-span CPU and memory deltas. Install [System Metrics](https://cbox.dk/packages/system-metrics/docs) alongside it and host CPU, memory, load and real RSS metrics appear on the next scrape. > **Zero cost when disabled**: With TELEMETRY_ENABLED=false every instrument becomes a no-op, no listeners register and the routes disappear. And telemetry never throws into your app: a broken exporter degrades to nothing, not to a 500. ## Installation ```bash composer require cboxdk/laravel-telemetry ``` ## Documentation Full documentation: [Telemetry for Laravel Docs](https://cbox.dk/packages/laravel-telemetry/docs/v2.md) ## Resources - [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/) - [W3C Trace Context specification](https://www.w3.org/TR/trace-context/) --- --- title: "Cbox ID JWKS Auth" description: "A JWKS-only validator and Laravel middleware for resource servers consuming Cbox ID-issued tokens. Caches the JWKS aggressively, validates JWTs locally, and tolerates ID being down via a grace window." github: "https://github.com/cboxdk/cbox-id-jwks-auth" packagist: "cboxdk/cbox-id-jwks-auth" language: "PHP" categories: - security - laravel url: "https://cbox.dk/packages/cbox-id-jwks-auth" docs: "https://cbox.dk/packages/cbox-id-jwks-auth/docs/v0.md" --- # Cbox ID JWKS Auth A JWKS-only validator and Laravel middleware for resource servers consuming Cbox ID-issued tokens. Caches the JWKS aggressively, validates JWTs locally, and tolerates ID being down via a grace window. Cbox ID JWKS Auth is the resource-server half of service auth: a JWKS-only validator and Laravel middleware for apps consuming Cbox ID-issued JWTs. High-volume gateways cannot afford a round-trip to the identity server on every request, so it caches the JWKS aggressively, validates tokens locally, and keeps serving from the last good key set for up to 24 hours if a fetch fails. ### Validate Locally - **JWKS-only validation**: Validates ID-issued JWTs against the published key set, with no per-request call back to the identity server. - **Aggressive caching**: The JWKS is cached and refreshed on a TTL, so validation stays in-process and fast even under heavy traffic. - **Tolerates ID downtime**: A configurable grace window keeps the last good JWKS serving up to 24 hours after a fetch failure, so an ID blip does not take your gateway down. - **Laravel middleware**: Auto-discovered service provider and middleware. Configure the issuer, JWKS URI and audience and protect a route. > **Pair with Cbox ID Tokens**: This package validates incoming tokens. When an app also needs to mint ID-issued tokens to call other services, pair it with cboxdk/cbox-id-tokens. Revocation is not honoured by JWKS-only validation; the short access-token TTL is the mitigation. ## Installation ```bash composer require cboxdk/cbox-id-jwks-auth ``` ## Documentation Full documentation: [Cbox ID JWKS Auth Docs](https://cbox.dk/packages/cbox-id-jwks-auth/docs/v0.md) ## Resources - [JSON Web Key Sets (RFC 7517)](https://www.rfc-editor.org/rfc/rfc7517) --- --- title: "Cbox ID Tokens" description: "A service-to-service OAuth helper for callers of Cbox ID-issued tokens. Sister apps install it to talk to Cbox ID and to each other through ID-issued bearer tokens, with caching built in." github: "https://github.com/cboxdk/cbox-id-tokens" packagist: "cboxdk/cbox-id-tokens" language: "PHP" categories: - security - laravel url: "https://cbox.dk/packages/cbox-id-tokens" docs: "https://cbox.dk/packages/cbox-id-tokens/docs/v0.md" --- # Cbox ID Tokens A service-to-service OAuth helper for callers of Cbox ID-issued tokens. Sister apps install it to talk to Cbox ID and to each other through ID-issued bearer tokens, with caching built in. Cbox ID Tokens is the caller half of service auth: a service-to-service OAuth helper for apps that need to obtain and use Cbox ID-issued bearer tokens. Webhooks, vault, and other sister apps install it to talk to the identity server and to each other. It fetches tokens via the client-credentials grant and caches them so you are not minting a new token on every call. ### Talk To Services - **Client-credentials grant**: Obtain ID-issued access tokens with your app client id and secret, ready to attach to outgoing requests. - **Cached tokens**: Tokens are cached in your app store (Redis recommended) and reused until near expiry, so services are not re-authenticating constantly. - **Zero-config discovery**: Auto-registers via Laravel package discovery. Point it at the token endpoint and set the client credentials in config. - **Pairs with JWKS Auth**: Use it alongside Cbox ID JWKS Auth when an app both mints tokens to call others and validates the tokens it receives. ## Installation ```bash composer require cboxdk/cbox-id-tokens ``` ## Documentation Full documentation: [Cbox ID Tokens Docs](https://cbox.dk/packages/cbox-id-tokens/docs/v0.md) ## Resources - [OAuth 2.0 client credentials](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) --- --- title: "fcgx" description: "FastCGI client library for Go. Context and timeout support, structured error handling. Built for talking to PHP-FPM." github: "https://github.com/cboxdk/fcgx" language: "Go" categories: - infrastructure - php url: "https://cbox.dk/packages/fcgx" docs: "https://cbox.dk/packages/fcgx/docs/v1.md" --- # fcgx FastCGI client library for Go. Context and timeout support, structured error handling. Built for talking to PHP-FPM. fcgx is a FastCGI client library for Go. It implements the FastCGI protocol with context support, structured error handling, and thread safety. It is the protocol layer that powers FPM Exporter. ### Why another FastCGI library? - **Context Support**: Every request accepts a context.Context for timeouts and cancellation. No more hanging connections to unresponsive PHP-FPM pools. - **Structured Errors**: Sentinel errors (ErrClientClosed, ErrTimeout, ErrConnRefused) that you can check with errors.Is() instead of parsing error strings. - **Thread Safety**: Fully safe for concurrent use. Run requests from multiple goroutines against the same client without synchronization. - **Unix + TCP**: Connect via Unix sockets (/var/run/php-fpm.sock) or TCP (127.0.0.1:9000). Automatic detection and connection handling. ## Built for monitoring Most FastCGI libraries are built for proxying HTTP requests. fcgx is built for monitoring. It is optimized for the pattern of sending a small request, reading a small response, and doing it thousands of times per minute with strict timeouts. Connection pooling, automatic reconnection, and context-aware cancellation make it reliable in production monitoring loops. > **Installation**: go get github.com/cboxdk/fcgx ## Documentation Full documentation: [fcgx Docs](https://cbox.dk/packages/fcgx/docs/v1.md) ## Resources - [FastCGI Protocol Specification](https://fastcgi-archives.github.io/FastCGI_Specification.html) --- --- title: "Geo for Laravel" description: "A canonical geography reference for Laravel: countries, subdivisions and currencies bound to stable ISO codes, resolved deny-by-default, carrying the tax-relevant attributes that billing and tax engines build on." github: "https://github.com/cboxdk/laravel-geo" packagist: "cboxdk/laravel-geo" language: "PHP" categories: - billing - laravel - php url: "https://cbox.dk/packages/laravel-geo" docs: "https://cbox.dk/packages/laravel-geo/docs/v0.md" --- # Geo for Laravel A canonical geography reference for Laravel: countries, subdivisions and currencies bound to stable ISO codes, resolved deny-by-default, carrying the tax-relevant attributes that billing and tax engines build on. Geo for Laravel is a canonical geography reference: countries, subdivisions and currencies bound to stable ISO codes, resolved deny-by-default, and carrying the tax-relevant attributes that billing and tax engines build on. It is the shared jurisdiction primitive for the portfolio. Identity organization addresses, billing invoice addresses and the tax engine all bind to the same jurisdiction keys, so nothing downstream matches on free text. ## Why it exists A tax or billing engine must never fuzzy-match a country name or guess a rate from a ZIP. Everything has to bind to a stable, validated jurisdiction key. This package provides that key: ISO 3166-1 alpha-2 countries, ISO 3166-2 subdivisions and ISO 4217 currencies, sourced from the vetted commerceguys/addressing dataset, with one layer added on top, the tax profile of each jurisdiction. ### The Jurisdiction Primitive - **Stable ISO Keys**: Countries, subdivisions and currencies keyed on ISO 3166 and ISO 4217, so every consumer binds to the same validated code instead of a free-text name. - **Tax Profile**: Each jurisdiction carries structural, slow-moving facts: regime (vat, gst, sales_tax, none), a stable regimeModule key, EU membership, sub-federal and rooftop flags. Not rates, those live in the tax engine feeds. - **Deny-By-Default**: An unknown country, an unknown subdivision, or a subdivision that does not belong to its country all resolve to null. Never a guess. - **Sub-Federal vs Rooftop**: Canada stacks tax at province level, so a subdivision is enough. The US stacks below the state and needs rooftop geocoding. The two are modelled separately rather than lumped together. - **Contracts-First**: Depend on the JurisdictionRepository contract; the default binding wraps the ISO dataset. Swap or persist the data without touching callers. - **No Forced Migration**: The reference data ships inside the library. Persisting jurisdictions for foreign-key integrity is an optional host concern, not a required migration. > **One key, every consumer**: Because identity, billing and tax all resolve against the same jurisdiction keys, a country or subdivision means exactly one thing across the portfolio. That is what lets a tax engine map a regimeModule to a rate feed without ever matching on a country name. ## Installation ```bash composer require cboxdk/laravel-geo ``` ## Documentation Full documentation: [Geo for Laravel Docs](https://cbox.dk/packages/laravel-geo/docs/v0.md) ## Resources - [commerceguys/addressing dataset](https://github.com/commerceguys/addressing) - [ISO 3166 country codes](https://www.iso.org/iso-3166-country-codes.html) --- --- title: "Spatie Permission Adapter for Cbox ID" description: "Adopt Cbox ID for authentication, SSO, OAuth/OIDC and SCIM while an existing spatie/laravel-permission install stays the source of roles, permissions and token claims. Binds the platform's authorization contracts to Spatie, with a SCIM group-to-role bridge that never revokes a manually granted role." github: "https://github.com/cboxdk/laravel-id-spatie" packagist: "cboxdk/laravel-id-spatie" language: "PHP" categories: - security - laravel - php url: "https://cbox.dk/packages/laravel-id-spatie" docs: "https://cbox.dk/packages/laravel-id-spatie/docs/v0.md" --- # Spatie Permission Adapter for Cbox ID Adopt Cbox ID for authentication, SSO, OAuth/OIDC and SCIM while an existing spatie/laravel-permission install stays the source of roles, permissions and token claims. Binds the platform's authorization contracts to Spatie, with a SCIM group-to-role bridge that never revokes a manually granted role. An application that already runs spatie/laravel-permission does not have to give up its RBAC to adopt Cbox ID. This adapter keeps Spatie as the single source of roles and permissions, and lets the platform handle authentication, enterprise SSO, the OAuth/OIDC provider and SCIM on top of it. The roles you already manage are stamped straight into the tokens the platform mints. > **Why an adapter, not a merge**: Cbox ID ships its own hierarchy-aware RBAC, but it never resolves authorization directly: it depends on the AccessChecker and Roles contracts. This package implements those against Spatie and binds them under the platform's external access-control driver, which gates the built-in RBAC tables off entirely. Two RBACs never run side by side, and the roles/permissions table names never collide. ### What It Provides - **Authorization Reads**: Permission checks and token claims resolve from Spatie's effective (direct plus role-derived) permissions and roles. An unknown permission or an unresolved subject denies rather than errors. - **Token Claims**: The OAuth/OIDC token issuer and the UserInfo endpoint read through the same contract, so your Spatie roles and permissions land in every token the platform issues. - **Role Authoring**: Defining roles, granting permissions and assigning or unassigning them drive into Spatie, so SCIM provisioning and access-governance campaigns write to the backend you already run. - **SCIM Group Bridge**: Map a directory group onto a role and the grant is kept in sync as membership changes, driven by the platform's directory membership event. - **Safe Reconciliation**: A ledger of the grants the bridge itself made means reconciliation only ever removes what it granted, so an administrator's hand-granted role survives a directory sync. - **Organization Scoping**: With Spatie's teams feature enabled each platform organization maps onto a team, so a role is scoped to the organization it was granted in. With teams off the backend is flat. ## The problem the bridge had to solve Spatie records that a role is held, but not why. A naive directory sync would therefore strip roles an administrator granted by hand the moment a user left a mapped group. The bridge keeps its own ledger of the grants it made, including a flag for a role the subject already held when a push landed, so revoking the push leaves the manual grant intact. Four cases are covered by tests: a pushed role is revoked when the subject leaves the group, a manual grant is kept, a manual grant later duplicated by a push is kept, and an administrator can still revoke their own grant. That ledger also restores grant-source fidelity the backend cannot express on its own. Assignments are reported as pushed or manual rather than uniformly manual, which is what access-certification campaigns display when a reviewer decides whether to keep or revoke an access grant. ## Adopting it Set the platform's access-control driver to external, point the adapter at your authorizable model, and add one interface to the model that already uses Spatie's HasRoles trait. Nothing else changes: callers keep depending on the platform contracts, and this package is inert until the external driver is selected, so installing it cannot disturb an app running the built-in RBAC. > **Honest scope**: The host's authorizable model must be keyed by the same identifier the platform uses for a subject, since that id is how a check finds the Spatie user. The platform's ancestor-organization roll-down is a built-in-driver feature and is not modelled by a flat Spatie backend. Mapping priority orders listings but does not override: a role is granted whenever any mapped group matches. ## Installation ```bash composer require cboxdk/laravel-id-spatie ``` ## Documentation Full documentation: [Spatie Permission Adapter for Cbox ID Docs](https://cbox.dk/packages/laravel-id-spatie/docs/v0.md) --- --- title: "Economic Nexus for Laravel" description: "The US economic-nexus engine for Laravel. Accumulate a seller's sales per state, detect when a post-Wayfair threshold is crossed, and report where registration is required. It owns the decision, you bind the sales data." github: "https://github.com/cboxdk/laravel-nexus" packagist: "cboxdk/laravel-nexus" language: "PHP" categories: - billing - laravel - php url: "https://cbox.dk/packages/laravel-nexus" docs: "https://cbox.dk/packages/laravel-nexus/docs/v0.md" --- # Economic Nexus for Laravel The US economic-nexus engine for Laravel. Accumulate a seller's sales per state, detect when a post-Wayfair threshold is crossed, and report where registration is required. It owns the decision, you bind the sales data. After South Dakota v. Wayfair, a seller with no physical presence in a state can still owe sales tax there once its cumulative sales cross that state's threshold. Economic Nexus for Laravel tracks that: it takes the seller's activity per state, compares it against the threshold, and tells you where a registration obligation has appeared. It is engine primitives, not a finished application, and it is UI-free. > **It never counts your sales for you**: Economic nexus turns on cumulative totals over a measuring period, which a single invoice cannot carry. So the package refuses to infer nexus from one supply and never accumulates sales itself. You bind a SalesLedger that answers with the seller's totals per state, and the engine owns the decision on top of it. ## Four states a state can be in Every state resolves to one status, in precedence order. Registered comes first: the seller already holds a registration there, so the obligation is handled. Then triggered by physical presence, which establishes nexus on its own whatever the sales. Then triggered economically, when cumulative activity crosses the threshold. Then approaching, when activity is inside a configurable band, 80 percent by default, which is a watch signal rather than an obligation. Otherwise below. Only triggered reports that action is needed. Each evaluation carries the threshold it was judged against, the activity behind it, a progress ratio toward crossing, whether physical presence applied, and a readable reason. A report buckets a set of states into triggered, approaching and registered, which is the shape a platform dashboard wants. ### What It Models - **Thresholds With Their Measurement**: A threshold is more than a dollar figure. It carries the crossing test, sales-only, sales-or-transactions, sales-and-transactions, plus the window the state measures over and which sales count. - **Measuring Periods**: Previous calendar year, current, previous-or-current, or rolling twelve months. The engine uses the figures; the period tells you how to accumulate your ledger so it matches what the state actually measures. - **Sales Basis**: Gross, retail or taxable sales, and whether marketplace-facilitated sales count toward the seller's own threshold. Getting this wrong is how a seller registers a year late. - **Physical Presence**: An office, employees or inventory in a state triggers nexus regardless of sales volume. Host-asserted through its own contract, and it outranks the economic test. - **Cited Threshold Data**: Thresholds come from the us-tax-data dataset, a dated-window list so a pending change is carried until it takes effect. A state the dataset does not cover returns nothing rather than a guess. - **Deny-By-Default Seams**: The three host contracts are empty until you bind them. No sales ledger means no economic determination, rather than a confident answer built on nothing. ## Where it sits next to the tax engine Tax for Laravel answers whether a single supply is taxable and at what rate, and it asserts nexus from an explicit seller registration rather than inferring it. This package answers the other question: given everything the seller has sold into a state so far, should there be a registration at all. One is per-invoice, the other is cumulative, and keeping them apart is why neither has to guess at the other's job. ## Installation ```bash composer require cboxdk/laravel-nexus ``` ## Documentation Full documentation: [Economic Nexus for Laravel Docs](https://cbox.dk/packages/laravel-nexus/docs/v0.md) ## Resources - [South Dakota v. Wayfair, Inc.](https://www.supremecourt.gov/opinions/17pdf/17-494_j4el.pdf) --- --- title: "Telemetry Store for Laravel" description: "A native ClickHouse OTEL store for Telemetry for Laravel. A PHP OTLP ingest endpoint plus a ClickHouse read driver for the Telemetry UI, so one SQL store replaces the Tempo, Loki and Mimir stack." github: "https://github.com/cboxdk/laravel-telemetry-store" packagist: "cboxdk/laravel-telemetry-store" language: "PHP" categories: - observability - laravel url: "https://cbox.dk/packages/laravel-telemetry-store" docs: "https://cbox.dk/packages/laravel-telemetry-store/docs/v1.md" --- # Telemetry Store for Laravel A native ClickHouse OTEL store for Telemetry for Laravel. A PHP OTLP ingest endpoint plus a ClickHouse read driver for the Telemetry UI, so one SQL store replaces the Tempo, Loki and Mimir stack. Telemetry Store for Laravel is a native ClickHouse OTEL store for [Telemetry for Laravel](https://cbox.dk/packages/laravel-telemetry). One SQL store instead of the Tempo, Loki and Mimir stack. It does two things: a native PHP OTLP/HTTP endpoint that receives the emitter traces, logs and metrics and writes them straight to ClickHouse with no OpenTelemetry Collector in the loop, and a ClickHouse-backed read driver for the Telemetry UI. ### One Store, Every Signal - **Collector-Free Ingest**: A native PHP OTLP/HTTP endpoint receives traces, logs and metrics from the emitter and writes them straight to ClickHouse. Point TELEMETRY_OTLP_ENDPOINT at this app, no Collector sidecar. - **ClickHouse Read Driver**: Registers as an extra connection driver for the Telemetry UI. Every existing dashboard card runs against ClickHouse unchanged, because the UI query layer is a backend-neutral IR each driver compiles to its own dialect. - **Selectable Per Connection**: An additional backend, not a replacement. Point logs at ClickHouse while keeping metrics on Prometheus, or move everything over. Your call, per connection. - **Collector-Compatible Schema**: Modelled on the OpenTelemetry Collector clickhouseexporter tables: otel_logs, otel_traces and otel_metrics. MergeTree, partitioned by day, with per-signal TTL for cheap partition-drop retention. > **Early**: The schema, ClickHouse client and OTLP ingest are the first milestone. The read drivers land signal by signal, logs then traces then metrics, matching the difficulty of expressing each in SQL. rate() and histogram_quantile() over cumulative series is the hard part. ## Install composer require cboxdk/laravel-telemetry-store, then php artisan telemetry-store:install creates the ClickHouse tables idempotently. Configure the ClickHouse connection and ingest in config/telemetry-store.php or via env, then select the ClickHouse driver in telemetry-ui.connections. For production, ClickHouse host access, retention, securing the ingest endpoint and throughput, see the deployment docs. ## Installation ```bash composer require cboxdk/laravel-telemetry-store ``` ## Documentation Full documentation: [Telemetry Store for Laravel Docs](https://cbox.dk/packages/laravel-telemetry-store/docs/v1.md) ## Resources - [OpenTelemetry Collector ClickHouse exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/clickhouseexporter) --- --- title: "Telemetry UI for Laravel" description: "Observability UI for Laravel that replaces Grafana for app-level monitoring. Queries your existing Tempo, Loki and Prometheus backends directly, with Laravel-shaped screens from metric aggregate down to the single trace." github: "https://github.com/cboxdk/laravel-telemetry-ui" packagist: "cboxdk/laravel-telemetry-ui" language: "PHP" categories: - laravel - observability url: "https://cbox.dk/packages/laravel-telemetry-ui" docs: "https://cbox.dk/packages/laravel-telemetry-ui/docs/v1.md" --- # Telemetry UI for Laravel Observability UI for Laravel that replaces Grafana for app-level monitoring. Queries your existing Tempo, Loki and Prometheus backends directly, with Laravel-shaped screens from metric aggregate down to the single trace. Telemetry UI for Laravel is the presentation counterpart to [Telemetry for Laravel](https://cbox.dk/packages/laravel-telemetry/docs). It queries your existing Tempo, Loki and Prometheus or Mimir backends directly, with no agent and no vendor cloud, and renders the answers as screens shaped like Laravel instead of general-purpose panels. It is schema-aware of every metric and span attribute the core package emits, so the screens are built for the data rather than assembled from it. ### Observability Shaped Like Laravel - **Laravel-Shaped Screens**: Dashboard, requests, jobs, commands, scheduled tasks, exceptions, queries, cache, outgoing HTTP, mail and notifications, users, logs and system. Not panels you assemble, screens that already know the framework. - **Aggregate to Trace**: Every screen cross-links from the metric aggregate to the individual trace. Spot a slow route in the histogram, land in its waterfall two clicks later. - **Fleet-Aware**: A service and environment switcher scopes every screen. One installation covers every app reporting to the same backends. - **Autodetecting Families**: Optional schema families light up their own pages when their metrics exist. Install Statamic Telemetry and the Statamic screens appear on their own. - **Livewire Card Extensibility**: Packages and apps add pages with PHP and Blade plus any PromQL, TraceQL or LogQL query. No JavaScript build required. - **Inert When Idle**: Boot registers class-string maps only, and one env var disables everything. Access is gated by a viewTelemetryUi gate, local-only by default. ## When Grafana is too generic Grafana is the right home for fleet-level monitoring, and the telemetry family ships dashboards for it. But a wall of general-purpose panels answers general-purpose questions. When the question is "what did this queue job actually do" or "which route is burning the CPU budget", the answer deserves a view built for Laravel: queue timelines, trace waterfalls and request breakdowns rendered inside a UI that speaks the vocabulary of the framework, at /telemetry-ui in the app you already run. > **Early days**: The package is in alpha. It expects the Grafana stack backends (Tempo, Loki, Prometheus or Mimir) that Telemetry for Laravel exports to, and it reads them directly over their HTTP APIs. ## Installation ```bash composer require cboxdk/laravel-telemetry-ui ``` ## Documentation Full documentation: [Telemetry UI for Laravel Docs](https://cbox.dk/packages/laravel-telemetry-ui/docs/v1.md) --- --- title: "Webhook Signature for Laravel" description: "Webhook signature verification for Laravel, with drivers for the providers you actually receive from: GitHub, Stripe, Slack, Shopify, Standard Webhooks, Twilio, Mailgun and Postal. Secret rotation, replay defence, and outbound signing that uses the same code." github: "https://github.com/cboxdk/laravel-webhook-signature" packagist: "cboxdk/laravel-webhook-signature" language: "PHP" categories: - security - laravel - php url: "https://cbox.dk/packages/laravel-webhook-signature" docs: "https://cbox.dk/packages/laravel-webhook-signature/docs/v0.md" --- # Webhook Signature for Laravel Webhook signature verification for Laravel, with drivers for the providers you actually receive from: GitHub, Stripe, Slack, Shopify, Standard Webhooks, Twilio, Mailgun and Postal. Secret rotation, replay defence, and outbound signing that uses the same code. Every webhook you receive has to be proven to come from who it claims to. That check is short enough that most codebases write it inline, and different enough per provider that they write it again for the next one. This package does the verification, ships a driver for each provider's actual scheme, and signs your outbound webhooks with the same code. > **Two lines to guard an endpoint**: Declare the endpoint and its secrets in config/webhook-signature.php, then put the webhook.signature middleware on the route. Inside the controller, VerifiedWebhook::fromRequestOrFail($request) hands you the payload that was actually signed, the provider's event id for idempotency, and which secret verified it. ## The providers, as they actually behave Nine bundled schemes, each signing something different. GitHub and Shopify sign the raw body, Stripe and Slack prefix a timestamp, Standard Webhooks prefixes an id and a timestamp, Twilio signs the URL with its parameters sorted byte-wise, and Mailgun signs a timestamp and token rather than the body at all. Most are hex, Shopify and Twilio are base64. Postal is not an HMAC: it is RSA against a public key. Anything else is either a config entry against the generic HMAC driver or a class you register. ### What A Hand-Rolled Verifier Misses - **Secret Rotation**: You cannot rotate a webhook secret atomically, because sender and receiver deploy separately. An endpoint holds a set of live secrets, and the verified webhook reports which one matched, so you know when the old one is genuinely unused. - **Replay Defence**: Timestamp binding where the provider supports it, plus optional single-use enforcement against a shared store. Off by default, deliberately: a guard backed by a per-node cache reports success while enforcing nothing. - **Typed Failure Reasons**: A missing secret on your side is not the same as a bad signature from the caller, and the status code says so: 401 for their mistake, 500 for yours. A provider retries a misconfigured receiver rather than discarding the event. - **No Silent Downgrades**: GitHub's legacy SHA-1 header is refused rather than accepted alongside SHA-256, and where Postal sends a SHA-256 header it is the only one checked, so a corrupted strong signature cannot fall back to the weak one. - **Signed At Send Time**: Http::webhookSignature() signs through Guzzle middleware, so the bytes signed are the bytes sent. There is no encoded body to hold in a variable and hand to two places that can disagree. - **Testing Helpers**: fakeWebhookEndpoint, postSignedWebhook and postUnsignedWebhook drive the production signing path, so a passing test exercises the same code the provider will hit. ## Checked against someone else's implementation A round trip cannot catch a misread specification, because the signer and the verifier are wrong in the same way. So every bundled scheme is tested against a signature produced by something other than this package: the worked examples GitHub, Slack, Standard Webhooks and Twilio publish, RFC 4231 vectors for the HMAC primitive itself, and for the rest, signatures generated by the provider's own SDK, the openssl CLI, or an independently written signer already running in production. The docs name the source for each one. None of the cryptography is hand-rolled. The MAC path is PHP's hash_hmac and hash_equals in a single class, and the asymmetric path is openssl_verify in another. > **What it deliberately is not**: No migrations, no models, no queued jobs, no webhook_calls table. Storing deliveries and processing them are decisions your application has already made, and a library that made them again would be something to fit around rather than something to drop in. ## Installation ```bash composer require cboxdk/laravel-webhook-signature ``` ## Documentation Full documentation: [Webhook Signature for Laravel Docs](https://cbox.dk/packages/laravel-webhook-signature/docs/v0.md) ## Resources - [Standard Webhooks specification](https://www.standardwebhooks.com) - [RFC 4231: HMAC test vectors](https://www.rfc-editor.org/rfc/rfc4231) --- --- title: "SIEM Core" description: "A zero-runtime-dependency SIEM log-streaming core for PHP. Produce one normalized security event and it hands you back exactly the bytes a real SIEM ingests: Splunk HEC, Elastic ECS, ArcSight CEF, Graylog GELF, or JSON." github: "https://github.com/cboxdk/siem" packagist: "cboxdk/siem" language: "PHP" categories: - security - observability - php url: "https://cbox.dk/packages/siem" docs: "https://cbox.dk/packages/siem/docs/v0.md" --- # SIEM Core A zero-runtime-dependency SIEM log-streaming core for PHP. Produce one normalized security event and it hands you back exactly the bytes a real SIEM ingests: Splunk HEC, Elastic ECS, ArcSight CEF, Graylog GELF, or JSON. SIEM Core is a zero-runtime-dependency log-streaming core for PHP. You produce one normalized security event and it hands you back exactly the bytes a real SIEM ingests: a Splunk HEC envelope, an Elastic ECS document, an ArcSight CEF line, a Graylog GELF message, or generic NDJSON. Nothing else. No HTTP, no queue, no credentials, no framework. ## Format here, deliver elsewhere Log streaming to a SIEM splits cleanly into two jobs, and this package is deliberately only the first. Formatting turns a normalized event into each SIEM wire schema: pure, deterministic, dependency-free, and security-critical, since CEF injection lives here. Delivery ships those records over the network with SSRF-guarded egress, batching, retries and secrets, and that is the [Laravel wrapper](https://cbox.dk/packages/laravel-siem). Keeping the formatting core free of I/O means the escaping that stops log injection is small, framework-agnostic and testable in isolation. ### One Event, Every Schema - **Normalized SiemEvent**: One immutable, transport-neutral value object: a stable id, timestamp, action, category, outcome, severity, optional actor and target, source IP, message, and an already-flattened context bag. - **Five Formatters**: SplunkHecFormatter, EcsFormatter, CefFormatter, GelfFormatter and JsonFormatter, each mapped to its target SIEM real schema. Swap one for another with no other change. - **Injection-Safe Escaping**: The security-critical part, the escaping that stops CEF and log injection, is isolated in the formatters, small enough to review and test on its own. - **StreamSink Interface**: A pure interface for delivery. The core ships no implementation beyond a FakeStreamSink for tests; the real sink is the delivery wrapper concern. - **Batch Is Just Map**: One record per event. A batch is the formatter mapped over many events, and framing (NDJSON newlines, HEC concatenation, syslog envelopes) is the transport job, not the formatter. - **Zero Runtime Dependencies**: PHP 8.4 and the standard library. Nothing to audit downstream, nothing to keep patched. > **Why the split matters**: The formatting core is pure and side-effect-free, so the part that must not be exploitable (log injection into a SIEM) is isolated from the part that must be operationally robust (network delivery). Each is testable and replaceable on its own. ## Installation ```bash composer require cboxdk/siem ``` ## Documentation Full documentation: [SIEM Core Docs](https://cbox.dk/packages/siem/docs/v0.md) ## Resources - [Elastic Common Schema (ECS)](https://www.elastic.co/guide/en/ecs/current/index.html) - [ArcSight Common Event Format (CEF)](https://www.microfocus.com/documentation/arcsight/arcsight-smartconnectors-8.4/cef-implementation-standard/) --- --- title: "Statamic Filter Builder" description: "Build dynamic collection filters and sort orders through the Statamic control panel. Visual filter builder without custom query scopes." github: "https://github.com/cboxdk/statamic-filter-builder" packagist: "cboxdk/statamic-filter-builder" language: "PHP" categories: - statamic url: "https://cbox.dk/packages/statamic-filter-builder" docs: "https://cbox.dk/packages/statamic-filter-builder/docs/v2.md" --- # Statamic Filter Builder Build dynamic collection filters and sort orders through the Statamic control panel. Visual filter builder without custom query scopes. Statamic Filter Builder adds visual filter and sort fields to the Statamic control panel. Editors build collection queries through a UI instead of asking developers for custom query scopes. No code changes, no deployments, no waiting. ### Visual Query Building - **Filter Builder Fieldtype**: A visual UI that presents field-aware operators. Text fields get "contains" and "starts with," dates get "before" and "after," toggles get "is true" and "is false." - **Sort Builder Fieldtype**: Companion field for defining sort orders. Editors choose the field and direction without touching template code. - **Dynamic Variables**: Reference values from the Antlers cascade in filter conditions. Filter by "current user" or "today's date" without hardcoding values. - **Auto-Scope Injection**: When filter_builder is present as a parameter, the query scope is applied automatically. No manual scope registration needed. ## Two modes Config mode locks the field to a specific collection and is ideal for fixed listing components. Field mode lets editors choose both the collection and its filters at runtime, enabling reusable listing components that work with any collection. > **Zero configuration**: composer require cboxdk/statamic-filter-builder. No config files, no publishing, no migrations. Add the fieldtype to a blueprint and start filtering. ## Installation ```bash composer require cboxdk/statamic-filter-builder ``` ## Documentation Full documentation: [Statamic Filter Builder Docs](https://cbox.dk/packages/statamic-filter-builder/docs/v2.md) --- --- title: "Statamic Reverse Relationship" description: "Traverse relationships in reverse in Statamic. If entry B points to A, let A see all the B's that reference it. No duplicated data, nothing to keep in sync." github: "https://github.com/cboxdk/statamic-reverse-relationship" packagist: "cboxdk/statamic-reverse-relationship" language: "PHP" categories: - statamic url: "https://cbox.dk/packages/statamic-reverse-relationship" docs: "https://cbox.dk/packages/statamic-reverse-relationship/docs/v2.md" --- # Statamic Reverse Relationship Traverse relationships in reverse in Statamic. If entry B points to A, let A see all the B's that reference it. No duplicated data, nothing to keep in sync. In Statamic, relationships are one-directional. If a blog post references an author, you can get the author from the post. But getting all posts by an author requires a separate query or duplicating the relationship on both sides. Statamic Reverse Relationship solves this by letting you traverse any relationship in reverse. ### Bidirectional Relationships - **Fieldtype**: Add to any blueprint. Displays related entries in the control panel and works like a native entries field in templates. No custom code needed. - **Antlers Tag**: Query reverse relationships from templates without blueprint changes. Perfect for one-off queries or dynamic listings. - **Editable Mode**: Attach and detach related entries directly from the control panel. Changes are saved to the source entry, maintaining a single source of truth. - **Lazy Query Builder**: Limit, sort, paginate, and count results. Queries are lazy-loaded to avoid performance issues with large datasets. ## Single source of truth The key principle: data is never duplicated. If entry B references A, the reverse relationship is computed at query time by scanning for entries that reference A. This means you never have stale or out-of-sync relationships, and there is nothing to migrate or maintain. > **Three modes**: Works with entries, taxonomy terms, and assets. If any content type references another, you can traverse the relationship in reverse. ## Installation ```bash composer require cboxdk/statamic-reverse-relationship ``` ## Documentation Full documentation: [Statamic Reverse Relationship Docs](https://cbox.dk/packages/statamic-reverse-relationship/docs/v2.md) --- --- title: "Statamic Telemetry" description: "Statamic overlay for Telemetry for Laravel. Content-aware trace names, Stache and static cache instrumentation, site context and user attribution as a proper Statamic addon." github: "https://github.com/cboxdk/statamic-telemetry" packagist: "cboxdk/statamic-telemetry" language: "PHP" categories: - statamic - observability url: "https://cbox.dk/packages/statamic-telemetry" docs: "https://cbox.dk/packages/statamic-telemetry/docs/v1.md" --- # Statamic Telemetry Statamic overlay for Telemetry for Laravel. Content-aware trace names, Stache and static cache instrumentation, site context and user attribution as a proper Statamic addon. Statamic Telemetry teaches [Telemetry for Laravel](https://cbox.dk/packages/laravel-telemetry/docs) Statamic's vocabulary. The core package already traces requests, queries, queue jobs and cache operations. This addon renames and enriches those signals with what actually matters in a Statamic site: which entry rendered, which collection it belongs to, whether the static cache hit, and what the Stache is doing. ### Traces That Speak Statamic - **Content-Aware Span Names**: Every Statamic frontend request goes through one catch-all route, so default span names collapse to a single useless value. Entry requests become GET entry:blog.article, term requests GET term:topics. Bounded names, raw route kept as an attribute. - **Static Cache Visibility**: Hit, miss and write outcome on the root span plus operation counters. Also fixes a subtle replay bug: the trace id header is stripped before the half-measure cacher snapshots it into the cached response. - **Stache Instrumentation**: Cache keys classified into bounded groups (stache.index, stache.item, stache.meta) so counters do not drown in thousands of raw keys. Warm and clear counters, warm duration histogram. - **Site Context**: The site handle rides as an ambient dimension on every span in the trace and propagates into queued jobs. Multi-site debugging without guessing. - **Statamic Metrics**: Counters for form submissions, Glide generations per preset, content changes by type and action, search index updates and auth events. Opt-in inventory gauges for entries, assets and users. - **Grafana Dashboard**: Bundles a Statamic dashboard for the core Grafana suite: static cache hit ratio, Stache traffic, content changes and Tempo panels for content traces and slow uncached pages. ## The catch-all route problem Generic APM tooling groups requests by route pattern. In Statamic, nearly every frontend request matches the same catch-all pattern, so an entire site collapses into one series and one span name. You can see that something is slow, but not what. The addon resolves the actual content behind the request and names the span after it, with entry id, collection, blueprint and taxonomy as attributes on the root span. One TraceQL query finds every slow article render across the fleet. The design notes are honest about the edges. Full-measure static cache hits never reach PHP and produce no telemetry at all. Static cache drivers are subclassed rather than decorated, because Statamic picks code paths with instanceof checks. And every listener wraps its body in a guard, so a broken listener can never break the entry save, upload or login it observes. > **Built on Telemetry for Laravel**: The addon requires cboxdk/laravel-telemetry and inherits its runtime model: metrics aggregate in Redis, Prometheus scrapes a route, traces ship as OTLP HTTP JSON. Everything is on by default except Antlers view spans and the inventory gauges. ## Installation ```bash composer require cboxdk/statamic-telemetry ``` ## Documentation Full documentation: [Statamic Telemetry Docs](https://cbox.dk/packages/statamic-telemetry/docs/v1.md) --- --- title: "Telemetry Browser" description: "A deep browser RUM SDK for Telemetry for Laravel: page load, Web Vitals, fetch/XHR, uncaught errors and SPA route changes, correlated with the backend trace. One trace id from click to browser span to request to query to job." github: "https://github.com/cboxdk/telemetry-browser" language: "TypeScript" categories: - observability url: "https://cbox.dk/packages/telemetry-browser" docs: "https://cbox.dk/packages/telemetry-browser/docs/v0.md" --- # Telemetry Browser A deep browser RUM SDK for Telemetry for Laravel: page load, Web Vitals, fetch/XHR, uncaught errors and SPA route changes, correlated with the backend trace. One trace id from click to browser span to request to query to job. Telemetry Browser is the frontend companion to Telemetry for Laravel: page load, Web Vitals, fetch and XHR, uncaught errors and SPA route changes, shipped to your app ingest endpoint and joined to the backend trace via W3C traceparent. One trace id gives you one waterfall, from the click through the browser span, the request span, the queries and the jobs. ### Real User Monitoring - **Correlated with the backend**: Emits the same dimensions as the backend and roots on the server trace via traceparent, so the Telemetry UI cross-links frontend and backend out of the box. - **Small and zero-config**: About 6 KB gzipped with Web Vitals included, and no OpenTelemetry SDK. A lean SDK tailored to the ingest endpoint, working with zero config to start. - **Web Vitals & errors**: Page load, LCP, CLS, INP and friends, plus fetch/XHR timings and uncaught errors, with SPA route changes tracked as their own spans. - **Never throws**: Best-effort by design: a failure in the SDK degrades to nothing, never into your app. > **Drop-in from the Laravel directive**: Install from npm as @cboxdk/telemetry-browser, or drop the standalone build in a script tag. It auto-inits from its own data attributes, the exact contract the Laravel package @telemetryBrowser directive emits. ## Documentation Full documentation: [Telemetry Browser Docs](https://cbox.dk/packages/telemetry-browser/docs/v0.md) ## Resources - [Web Vitals](https://web.dev/articles/vitals) - [W3C Trace Context](https://www.w3.org/TR/trace-context/) --- --- title: "phpfpm" description: "A Go library for talking to PHP-FPM: discover running masters, parse their effective configuration, scrape live status over FastCGI, and reload a master safely. The shared layer beneath fpm-exporter and fpm-tune." github: "https://github.com/cboxdk/phpfpm" language: "Go" categories: - infrastructure - php url: "https://cbox.dk/packages/phpfpm" docs: "https://cbox.dk/packages/phpfpm/docs/v1.md" --- # phpfpm A Go library for talking to PHP-FPM: discover running masters, parse their effective configuration, scrape live status over FastCGI, and reload a master safely. The shared layer beneath fpm-exporter and fpm-tune. Two of our tools need the same things from PHP-FPM: find the masters running on a host, work out what they are actually configured to do, read what their pools are doing right now, and in one case change it. fpm-exporter reads a host, fpm-tune reads and writes one. Neither depends on the other, and both depend on this. > **Validate, then reload**: SIGUSR2 makes a master re-read its configuration and cycle workers gracefully, carrying its listening sockets across so no request is dropped. But a configuration php-fpm rejects does not degrade: the master refuses to come back, and every pool it served goes with it. Anything changing pm.* must call Validate first. A drop-in naming a pool that no longer exists makes php-fpm -t exit 78, and reloading with that file present kills the master permanently. ## The parts that refuse to do the obvious thing The interesting code here is the code that declines the easy implementation, because the easy one is wrong in a way that only surfaces in production. Each of these was found against a real master rather than reasoned about. ### Found The Hard Way - **A Reload Does Not Preserve The PID**: SIGUSR2 makes the master re-exec itself. In the foreground the pid survives; daemonized, which is php-fpm's own default, the re-exec produces a new process and the original exits. A consumer that watched the pid it signalled reported a textbook reload as a dead master and rolled a good change back. - **Identity Before Signalling**: A pid is a promise about the instant it was read. A master can exit between discovery and the reload and the kernel can reuse the number, so the reload compares process start time across the settle window, and matches the config path as a path rather than a substring so a host running two masters signals the right one. - **The Process Table Is Not A Trust Boundary**: Any local user can start a process named like php-fpm whose command line names a config they control, and both get handed to exec. Discovery checks ownership, refuses relative paths, and as root checks the directories above them, since a root-owned binary in a world-writable directory can be swapped between the check and the exec. - **Finding A Master Without Reading Its Config**: Discover parses each master's config to enumerate pools, so a master whose configuration no longer parses is invisible to it, which is exactly when something trying to repair that configuration most needs to find it. DiscoverMasters reads only the process table and answers anyway. - **The Config Cache Never Expires**: Parsing forks php-fpm, so the result is cached for the life of the process. Right for a scrape loop, wrong for anything that changes the configuration. A consumer that missed the invalidation call reported a pool as configured for 4 workers hours after setting it to 12. - **An Unscrapable Pool Still Costs Memory**: A failed scrape still carries the pool's configured max_children and process manager. Without that, a site restarting for five seconds looks like a pool needing nothing, and its memory gets handed to its neighbours. ## Where it runs, and as whom It shells out to the php-fpm binary to parse and validate, scans /proc to find masters, and signals them, so it runs where php-fpm runs, which in practice means Linux. Elsewhere the process scan and the trust checks degrade rather than pretend. The user matters too: as root it reads everything and applies the strict ownership checks, as the php-fpm user it reads its own master, and as a stranger it sees less, on purpose. There is no package-level logger. Every entry point that can log takes a logger, because a library owning a global one cannot be embedded twice with different destinations and makes tests order-dependent. ## Documentation Full documentation: [phpfpm Docs](https://cbox.dk/packages/phpfpm/docs/v1.md) --- --- title: "US Tax Dataset" description: "A versioned JSON dataset of US sales-tax data, compiled from official government sources with per-record provenance: state and local rates with effective windows, economic-nexus thresholds, per-state taxability and intrastate sourcing, for all 51 jurisdictions." github: "https://github.com/cboxdk/us-tax-dataset" categories: - billing url: "https://cbox.dk/packages/us-tax-data" docs: "https://cbox.dk/packages/us-tax-data/docs/v0.md" --- # US Tax Dataset A versioned JSON dataset of US sales-tax data, compiled from official government sources with per-record provenance: state and local rates with effective windows, economic-nexus thresholds, per-state taxability and intrastate sourcing, for all 51 jurisdictions. This is not a package you require, it is data you read. US sales tax is fifty separate systems, and the hard part was never the arithmetic: it was getting figures you can stand behind, dated, sourced, and kept current. This dataset is the output of a pipeline that fetches the official state files, hashes them, and records where every figure came from. > **Read at runtime, not shipped in a package**: Tax for Laravel and Economic Nexus for Laravel both bind to it and fetch it at runtime, so a rate change is a dataset release rather than a package upgrade. Pin the location at a tagged release for a deterministic build. Disable it and the engines fall back to their static tables rather than guessing. ## What is in it ### Four Planes, All 51 Jurisdictions - **Rates**: State baselines for every state and DC, plus local rate records: 41 states carry them from official files, 5 more are complete on their state rate alone because no local sales tax exists, and Idaho is marked partial because locals exist that the dataset does not carry. - **Taxability**: Per-state, per-category determinations across 25 product categories, from prescription drugs and groceries to SaaS, digital goods and AI services. Each one carries a dated citation. - **Economic Nexus**: Post-Wayfair thresholds for 47 jurisdictions, with the measuring period, the sales basis, and whether marketplace sales count toward the seller's own threshold. - **Sourcing**: The intrastate origin, destination or mixed rule per state, which decides whether an in-state sale is taxed where it ships from or where it lands. - **Per-Record Provenance**: Automated figures carry the source file's URL, name, sha256 and retrieval time. Curated figures carry a dated citation. Every number can be traced back to something you can check. - **Effective Windows**: Records carry effective-from and effective-to dates, so a rate change taking effect next quarter is already in the dataset waiting for its date rather than arriving late. ## A gap is a stated fact, never a silence Every state carries an explicit coverage level, so a missing local rate is visible rather than implied. Where two published compilations disagree on whether something is taxable, the determination is left out instead of picked. Curated figures cannot drift quietly either: wherever an official file gives ground truth the curated baseline has to agree with it or the build aborts, and a monthly check fails when any citation ages past 120 days without a human re-verifying it. It is semver tagged and carries its own schema version. Additive fields are not breaking, so a consumer written against an older release keeps working, and a schema bump is the signal that something was removed or reinterpreted. > **Internal use, including commercial**: Licensed under PolyForm Internal Use 1.0.0. Computing the tax on your own invoices is internal use and covered, commercial or not. Handing the rates themselves to your customers, as data, an API, or a feature of your product, is distribution and needs a separate licence. It is also not tax advice: the provenance trail exists so you can verify against the official files where statutory reliance matters. ## Documentation Full documentation: [US Tax Dataset Docs](https://cbox.dk/packages/us-tax-data/docs/v0.md) ## Resources - [PolyForm Internal Use License 1.0.0](https://polyformproject.org/licenses/internal-use/1.0.0/) - [South Dakota v. Wayfair, Inc.](https://www.supremecourt.gov/opinions/17pdf/17-494_j4el.pdf) --- --- title: "fpm-tune" description: "Sizes PHP-FPM pools against the memory a machine actually has, using the memory its workers actually use. It measures what each pool costs, divides the budget, and writes it back. Beta: it writes production configuration." github: "https://github.com/cboxdk/fpm-tune" language: "Go" categories: - infrastructure - php url: "https://cbox.dk/packages/fpm-tune" docs: "https://cbox.dk/packages/fpm-tune/docs/v1.md" --- # fpm-tune Sizes PHP-FPM pools against the memory a machine actually has, using the memory its workers actually use. It measures what each pool costs, divides the budget, and writes it back. Beta: it writes production configuration. A server running many sites has many pools competing for one pool of RAM. Setting pm.max_children by hand means either leaving capacity unused or finding the ceiling through an OOM kill. fpm-tune measures what each pool's workers really cost, divides the budget accordingly, and writes it back. It runs standalone: its own discovery, its own measurement, its own budget detection, its own metrics. > **Beta, and it writes production config**: The tags are prereleases, and it changes pm.* settings on a live host and reloads the master. Start with plan, which writes nothing, or serve --recommend, which watches and writes its conclusion to a file nothing loads. A daemon without --apply changes nothing and never will. ## Three commands plan shows what it would change and why, and writes nothing. apply writes the settings and reloads once. serve keeps measuring and publishes metrics, touching nothing until you add --apply. Watching without acting is a first-class way to run it rather than a step on the way to something else, and --recommend gives that daemon somewhere to put its conclusion: PHP-FPM configuration you can read, diff and paste, rewritten only when the recommended settings actually change, so its modification time answers when the advice last moved rather than whether the daemon is up. Every pool comes with the evidence for its number: the peak workers busy, the measured memory per worker, and the median, p95, p99 and worst readings behind it. One number cannot answer the question on its own. ### What Makes The Measurement Honest - **Never Learns From An Idle Pool**: A quiet pool's workers give memory back to the operating system, so they read small. That is a lull, not a cheaper application. A pool must be serving at least a request a second before a smaller reading counts as evidence. Below that threshold a wrong answer wastes memory; above it a wrong answer loses the host. - **Adjusts Rather Than Pins**: The estimate rises quickly when a pool gets more expensive and falls on a half-life measured in time, not in samples. Sizing to a percentile of the day pins the host to its busiest hour, and a per-sample decay lets the scrape interval silently change the behaviour. - **Time Only Counts If It Was Watched**: Elapsed time is the weight, which is right while the looking is regular and wrong the moment it stops. Each pool remembers how often it is actually looked at, so a gap after a restart can never move the estimate further than one ordinary scrape. - **Follows The Peak Of The Sawtooth**: max_requests recycles workers, so memory climbs and resets. The peak is the number that has to fit, and PHP-FPM resets its own high-water marks on reload, so the peak is remembered here instead. - **Charges For Spawned Children**: A worker that shells out to ffmpeg or imagemagick starts a separate process with memory the worker's RSS does not include. The budget is charged for it anyway, so sizing on worker memory alone prices the child at zero and overcommits the host the moment the pool gets busy. - **Measures Small Pools Too**: A pool that never runs two workers at once, or recycles them before they warm up, was invisible to a stricter version of this and got a table's guess for ever. Its readings now count toward what it costs, though still not toward permission to shrink it. ## Built to fail safe It touches only pm.* keys, in one file of its own inside the directory your master already includes. Your pool config is never edited, and deleting that file returns everything to what you configured. The change set is one atomic rename, so a growth and the reduction that funds it reach the host together or not at all, and it is validated against a sandboxed copy first so a configuration php-fpm would reject never reaches the directory it globs, not even for the length of a fork. What is about to be written is recorded first, with a phase, so an interrupted run is finished or undone on the next start, and a rollback is rehearsed before it is performed. The backup directory is not scratch space: it holds the previous file and a note of where php-fpm lives, which is how the tool repairs a host whose master will not start. That is the one situation where nothing can be discovered, because there is nothing running to discover. A tmpfiles rule that cleans that directory takes away both the undo and the repair. ## Documentation Full documentation: [fpm-tune Docs](https://cbox.dk/packages/fpm-tune/docs/v1.md) --- --- title: "telemetryd" description: "A single-binary, zero-config observability backend. OTLP goes in, the Loki, Tempo and Prometheus query APIs come out, and everything lives in one directory on one node. No sidecars, no stack to operate." github: "https://github.com/cboxdk/telemetryd" language: "Rust" categories: - observability - infrastructure url: "https://cbox.dk/packages/telemetryd" docs: "https://cbox.dk/packages/telemetryd/docs/v0.md" --- # telemetryd A single-binary, zero-config observability backend. OTLP goes in, the Loki, Tempo and Prometheus query APIs come out, and everything lives in one directory on one node. No sidecars, no stack to operate. Running your own observability normally means running a stack: Loki for logs, Tempo for traces, Prometheus or Mimir for metrics, and something to put in front of them. telemetryd is one binary that accepts all three signals as OTLP and serves them back through the same Loki, Tempo and Prometheus query APIs those tools expose, so anything that already speaks them keeps working. One port, one data directory, no sidecars. > **The whole setup**: telemetryd serve. It listens on 127.0.0.1:4319, stores data in a local directory, keeps 7 days of logs and traces and 30 days of metrics, and holds itself under a 10 GiB disk budget. Point Telemetry for Laravel at that address, and point all three of the Telemetry UI connectors at the same base URL. ## Single-node on purpose It is scoped at one team, a handful of apps, one VPS or a laptop, and it targets that case completely rather than scaling to a fleet. That is a design choice rather than a limitation waiting to be removed: it is what allows sharding, consensus, object-store tiering and a query scheduler to be deleted outright, which is the difference between one 2.6 MB binary and a set of services to operate. If you outgrow one node, you have outgrown telemetryd, and it says so rather than pretending. ### What It Does - **Three Signals, One Store**: Logs, traces and metrics all arrive as OTLP over HTTP, with Prometheus remote_write accepted too, and land in Parquet segments in a single directory. - **The APIs You Already Query**: Reads come back through the Loki, Tempo and Prometheus query APIs, including live tail. The compatibility contract is derived from the Telemetry UI connector source rather than from the upstream references, so it matches what actually gets called. - **Retention With A Disk Budget**: Retention enforces both an age and a size ceiling, so a burst of logs cannot quietly fill the volume while the time-based rule still looks satisfied. - **Refuses To Be Open By Accident**: It will not start on a non-loopback address without a token configured. The container image binds all interfaces, so it generates tokens on first start and prints them once. - **Your Data Can Leave**: export and import move every signal in both directions as OTLP. Between two telemetryd instances records are read straight from the store rather than re-derived from a query language, so they cross as stored. - **Installs Like A Service**: An install script that verifies the checksum always and the release signature when cosign is present, a Homebrew tap, a deb, a container image, and a hardened systemd unit or launchd plist you can print and read before installing. ## Honest about the gaps The repository carries a build-status document listing what works, what the known gaps are, and what is deliberately absent, kept next to the code rather than in a roadmap. The part worth reading before pointing a query tool at it is which Loki and Prometheus constructs are refused: someone who knows those languages will otherwise write valid expressions this does not run. ## Documentation Full documentation: [telemetryd Docs](https://cbox.dk/packages/telemetryd/docs/v0.md) ## Resources - [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/) --- --- title: "PHP Base Images" description: "PHP Docker base images with extensions, process management, and sensible defaults. Four tiers (Slim, Standard, Chromium, Dev) on Debian 12 with Cbox Init built in." github: "https://github.com/cboxdk/php-baseimages" language: "Shell" categories: - infrastructure - php url: "https://cbox.dk/packages/php-baseimages" docs: "https://cbox.dk/packages/php-baseimages/docs/v1.md" --- # PHP Base Images PHP Docker base images with extensions, process management, and sensible defaults. Four tiers (Slim, Standard, Chromium, Dev) on Debian 12 with Cbox Init built in. PHP Base Images gives you Docker images with PHP extensions, a process manager, and sensible defaults already set up. Three tiers cover everything from slim API services to full CMS installations, all on Debian 12 with Cbox Init as the process manager. ### Three Tiers - **Slim**: Minimal PHP-FPM with essential extensions only. For APIs, microservices, and applications that manage their own dependencies. Smallest possible image size. - **Standard**: PHP-FPM with common extensions (GD, intl, zip, pdo_mysql, pdo_pgsql, redis). Covers most Laravel and Symfony applications out of the box. - **Chromium**: Everything in Standard plus imagick, swoole, pcntl, sockets, and more. For CMS platforms (WordPress, Statamic, Magento) and applications with heavy extension requirements. - **Dev**: Everything from Chromium but with developer tools like Xdebug, PCOV and SPX. ## Why not the official images? The official PHP Docker images ship without extensions, without a process manager, and without sensible defaults. Every team ends up writing their own Dockerfile that installs extensions, configures PHP-FPM, sets up Nginx, and wires it all together. PHP Base Images gives you a tested, maintained starting point. > **Cbox Init included**: All tiers include Cbox Init as the process manager. PHP-FPM auto-tuning, health checks, and multi-process orchestration work out of the box. ## Documentation Full documentation: [PHP Base Images Docs](https://cbox.dk/packages/php-baseimages/docs/v1.md) --- --- title: "Health for Laravel" description: "Health checks, Kubernetes probes, Prometheus metrics, and system monitoring for Laravel applications." github: "https://github.com/cboxdk/laravel-health" packagist: "cboxdk/laravel-health" language: "PHP" categories: - laravel - observability url: "https://cbox.dk/packages/laravel-health" docs: "https://cbox.dk/packages/laravel-health/docs/v2.md" --- # Health for Laravel Health checks, Kubernetes probes, Prometheus metrics, and system monitoring for Laravel applications. Health for Laravel provides health check endpoints for Kubernetes probes, Prometheus metrics, and general system monitoring. Define checks for your database, Redis, queue, disk, and custom services, then expose them as HTTP endpoints that Kubernetes and monitoring tools understand. ### Production Health Monitoring - **Kubernetes Probes**: Separate endpoints for liveness, readiness, and startup probes. Each probe can run different checks with independent thresholds. - **Prometheus Metrics**: Health check results exposed as Prometheus gauges. Track check durations, success rates, and failure patterns over time. - **Built-in Checks**: Database connectivity, Redis availability, queue processing, disk space, and memory usage. Add custom checks with a simple interface. - **System Metrics Integration**: Uses the System Metrics library for container-aware resource checks. Memory and CPU thresholds respect cgroup limits. ## Probe configuration Each Kubernetes probe type has different requirements. Liveness probes should be fast and only check if the process is alive. Readiness probes should check dependencies (database, cache). Startup probes give slow-starting applications time to initialize. Health for Laravel lets you assign different checks to each probe type. > **Quick start**: composer require cboxdk/laravel-health. Health endpoints are available immediately at /health/liveness, /health/readiness, and /health/startup. ## Installation ```bash composer require cboxdk/laravel-health ``` ## Documentation Full documentation: [Health for Laravel Docs](https://cbox.dk/packages/laravel-health/docs/v2.md) ## Resources - [Kubernetes Probe Configuration](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) --- ## Blog - [PHP Base Images v1](https://cbox.dk/blog/php-base-images-v1.md): The images size their own FPM pool from measured memory and expose the queue-depth signals a scale-out decision needs. What they didn't have was version numbers. v1 adds releases, a channel tag that gets weekly security patches without crossing a tooling major, and tags that only move after the image passes. - [pm.max_children Is a Guess](https://cbox.dk/blog/pm-max-children-is-a-guess.md): Every PHP-FPM config starts with a number someone guessed. Fine for one app, but on a box running fifty pools that share the same RAM, it's how you starve some sites and pay for idle workers on others. So I built a tool that measures what workers actually cost and sizes the ceilings from that. - [How Hard Can US Sales Tax Be](https://cbox.dk/blog/how-hard-can-us-sales-tax-be.md): I was building a billing engine, so I had to handle tax. The EU was hard but knowable. Then I looked at the US and said the sentence that sits right at the peak of the Dunning-Kruger curve. What came out the other side was not tax logic, it was an ETL pipeline against forty government websites. - [One Identity Layer Instead of Five](https://cbox.dk/blog/one-identity-layer-instead-of-five.md): Every product needs login, organizations, roles, SSO and an audit trail. I had built that more than once, a little differently each time. Identity is the last thing you want five diverging copies of, so it became laravel-id: a contract-driven auth and identity framework for Laravel. - [The Code We Kept Rewriting](https://cbox.dk/blog/the-code-we-kept-rewriting.md): SSRF guards, risk scoring, jurisdiction lookups, DNS reads. The same logic was already in production across several projects, written fresh each time and tuned to each one. Maintaining five copies stopped making sense, so I pulled each into a single open source package. - [Why I Built a Collector-Free Telemetry Package for Laravel](https://cbox.dk/blog/why-i-built-collector-free-telemetry-for-laravel.md): Sentry and Nightwatch showed green while customers saw errors. Nothing ever threw, and sampling dropped exactly the requests that mattered. That incident, collector fatigue on Kubernetes, and a family of packages that all needed metrics became "Telemetry for Laravel". - [Queue Autoscale v3 is here](https://cbox.dk/blog/laravel-queue-autoscale-v3-is-here.md): A predictive scaling rewrite, worker topology with exclusive and grouped queues, and multihost cluster orchestration. Plus the embarrassing bug that has been silently overprovisioning workers by 1000x. - [Launching Queue Monitor for Laravel](https://cbox.dk/blog/launching-laravel-queue-monitor.md): Full queue visibility without Horizon or Redis. Queue Monitor for Laravel tracks every job on any driver (database, SQS, Beanstalkd) with one composer require and a migration. - [Launching Health for Laravel](https://cbox.dk/blog/launching-laravel-health.md): I released cboxdk/laravel-health today. Installed it on cbox.dk, opened the dashboard, and immediately found two bugs. The OS version showed "unknown" and my Hetzner VM was detected as a container. Dogfooding works. - [New Website for Dansk Pinto Forening](https://cbox.dk/blog/new-website-for-dansk-pinto-forening.md): I launched pinto.dk today. Built with Statamic, with collections for breeding programs, stallion registries, rating results, and a horse database mapped from decades of breeding data. - [Statamic MCP v2.0: Out of Beta](https://cbox.dk/blog/statamic-mcp-v2-is-here.md): Statamic MCP v2 is released. OAuth 2.1 for ChatGPT, file and database storage drivers, a full CP dashboard, 21 scoped permissions, audit logging, and a router architecture that replaced 140+ tools with 11. Statamic 6 only. - [Predictive Queue Autoscaling with Little's Law](https://cbox.dk/blog/predictive-queue-autoscaling-with-littles-law.md): How I built an SLA-driven autoscaler for Laravel queues using queueing theory, trend prediction, and backlog drain algorithms. Replacing arbitrary worker counts with math. - [Zero-Dependency System Metrics in Pure PHP](https://cbox.dk/blog/zero-dependency-system-metrics-in-pure-php.md): Why I built a system metrics library with no extensions, no Composer dependencies, and no C bindings. Reading /proc and sysctl directly gives you better container awareness than most monitoring agents. - [Monitoring PHP-FPM in Production: A Complete Stack](https://cbox.dk/blog/monitoring-php-fpm-in-production.md): How Cbox FPM Exporter and Cbox Init work together to give you full observability over PHP-FPM pools, OPcache, and Laravel queues. From FastCGI status pages to Prometheus dashboards. - [Safety Nets for Claude Code with --dangerously-skip-permissions](https://cbox.dk/blog/safety-nets-for-claude-code-skip-permissions.md): The flag skips permission prompts, not all safety controls. Hooks can block tool calls before execution. Deny rules still apply. Sandboxing still restricts filesystem and network. Here is how to layer them. - [Health Checks and Kubernetes Probes for Laravel](https://cbox.dk/blog/health-checks-kubernetes-probes-laravel.md): Liveness, readiness, and startup probes are how Kubernetes decides if your pod is healthy. Health for Laravel gives you a clean API for all three, plus Prometheus metrics. - [Redesigning Statamic MCP: Fewer Tools, Better AI Reasoning](https://cbox.dk/blog/redesigning-statamic-mcp-fewer-tools-better-reasoning.md): Version 1 exposed 140+ individual tools. Version 2 consolidates them into 11 router-based tools. Here is why fewer tools means better AI performance. - [Connecting Statamic to AI Assistants with MCP](https://cbox.dk/blog/connecting-statamic-to-ai-with-mcp.md): The Model Context Protocol lets AI assistants interact with your Statamic site directly. Here is how Statamic MCP gives Claude, Cursor, and ChatGPT access to your content model. - [Reverse Relationships in Statamic: A Single Source of Truth](https://cbox.dk/blog/reverse-relationships-single-source-of-truth.md): If entry B references A, why can't A see all the B's that reference it? Statamic Reverse Relationship solves bidirectional traversal without data duplication. - [Visual Collection Filtering in Statamic Without Code](https://cbox.dk/blog/visual-collection-filtering-in-statamic.md): Your editors should not need a developer every time they want to filter a collection differently. Statamic Filter Builder adds a visual query builder to the control panel. - [Open Source Momentum](https://cbox.dk/blog/open-source-momentum.md): A year at Laravel gave me experience I could not have gotten anywhere else. Now that chapter is done, and there is a backlog of open source work to ship. Statamic MCP rewrite, new addons, and more infrastructure tooling. - [Leaving Laravel](https://cbox.dk/blog/leaving-laravel.md): I have decided to leave Laravel at the end of this month. It has been a dream job. I worked on the queue manager, custom eBPF projects, and other systems that push the boundaries of what we can do at scale. - [Laracon US: Meeting the Team](https://cbox.dk/blog/laracon-us-meeting-the-team.md): Back home in Denmark after Laracon US. I joined Laravel in October and this week around 50 of us were together for the first time. I had met a few at Laracon EU, but this was different. - [Blood, Sweat & Kubernetes at Laravel Hungary](https://cbox.dk/blog/blood-sweat-and-kubernetes-at-laravel-hungary.md): I gave my first talk as part of the Laravel team at the Laravel Hungary meetup in Budapest. Here is the recording and some takeaways from presenting Laravel Cloud infrastructure to the Hungarian community. - [You Know Laravel. Do You Know How It Runs?](https://cbox.dk/blog/you-know-laravel-do-you-know-how-it-runs.md): I spoke at the Laravel Denmark Copenhagen Meetup about what happens when your Laravel app actually runs. From php artisan serve to php-fpm, Octane, and FrankenPHP - the runtime matters more than most developers think. - [Blood, Sweat & Kubernetes at LaraFest](https://cbox.dk/blog/blood-sweat-and-kubernetes-at-larafest.md): I brought the Blood, Sweat & Kubernetes talk to LaraFest in the Netherlands. A proper conference stage, a packed room, and the Dutch Laravel community asking hard questions about how Laravel Cloud actually works. - [Laracon EU: One Month In, Three Regions Deep](https://cbox.dk/blog/laracon-eu-one-month-in-three-regions-deep.md): I started as a contractor in October and went full-time in January. By Laracon EU I had deployed three full regions for the keynote. Then Taylor clicked a button on stage and nothing happened. My console went red. My heart stopped. - [Joining Laravel to Build Laravel Cloud](https://cbox.dk/blog/joining-laravel-to-build-laravel-cloud.md): I am joining the Laravel team as Senior Infrastructure Engineer to help build Laravel Cloud. My cloud platform project is going on hold. Here is why this is the right move. - [Cache Me If You Can](https://cbox.dk/blog/cache-me-if-you-can.md): A talk I gave at the Laravel Aarhus Meetup on caching strategies in Laravel. From basic cache-aside to cache warming, tagged invalidation, and the patterns I use at TV2 to handle 90,000 requests per second. - [Building a Managed Platform for PHP](https://cbox.dk/blog/building-a-managed-platform-for-php.md): For the past year, I have been quietly building a managed hosting platform for PHP applications. Auto-scaling, metrics-driven infrastructure, and zero-config deployments. Here is what I have learned so far. - [Your PHP Container Metrics Are Lying to You](https://cbox.dk/blog/your-php-container-metrics-are-lying-to-you.md): Most monitoring tools report host resources instead of container limits. When your container has 512MB but the host has 64GB, your memory usage graph shows 0.7% instead of the real 85%. - [Statamic at Scale: My Talk at Laravel Live DK 2024](https://cbox.dk/blog/statamic-at-scale-laravel-live-dk-2024.md): I presented Statamic at Scale at Laravel Live Denmark 2024 together with the Statamic team. 1.74 billion requests per month, 90,000 requests per second during elections, and 40+ custom addons. Here is the story. - [PHP Docker Images: Tiers, Not Dockerfiles](https://cbox.dk/blog/php-docker-images-three-tiers-one-philosophy.md): Every team builds their own PHP Docker images. Most end up with 800MB images full of build tools. Here is how I designed a set of tiers that cover almost every use case without a custom Dockerfile. - [Building a FastCGI Client from Scratch in Go](https://cbox.dk/blog/building-a-fastcgi-client-from-scratch-in-go.md): The FastCGI protocol is from 1996 but still powers most PHP applications. Here is what I learned implementing a client library with context support and proper error handling. - [Flatcamp 2024 in Rome](https://cbox.dk/blog/flatcamp-rome-2024.md): Drove to Rome with my good friend Jacob for Flatcamp, the Statamic community gathering at a private Italian villa. I presented liveblogs with WebSockets and Livewire. Met most of the Statamic community for the first time. - [Why We Need a Real PID 1 for PHP Containers](https://cbox.dk/blog/why-we-need-a-real-pid-1-for-php-containers.md): Supervisord and shell scripts are not process managers. Here is why your PHP containers need proper signal handling, zombie reaping, and health-aware startup ordering. - [Scaling with Websockets](https://cbox.dk/blog/scaling-with-websockets.md): I gave a talk at the Laravel Copenhagen Meetup on scaling websocket connections in Laravel. Broadcasting, Pusher vs self-hosted, horizontal scaling, and what happens when 10,000 users connect at once. - [From Drupal to Bazo to Statamic: The TV2 Regionerne Story](https://cbox.dk/blog/from-broadcast-cms-to-statamic-lessons-from-tv2.md): I built two CMS platforms for Denmark's regional broadcaster. First Bazo, a headless Laravel + Nuxt system that replaced eight Drupal sites without downtime. Then Statamic, with 200+ core contributions and 40+ addons to make it scale horizontally on Kubernetes. - [Why I Started Cbox](https://cbox.dk/blog/why-i-started-cbox.md): Cbox started as an online PDA built with PHP and Flash before I turned 18. The name was originally Combo-Box but that sounded wrong, so Cbox was born. Twenty years later, the name stuck but everything else changed. - [Migrating a Million Videos Without Downtime](https://cbox.dk/blog/migrating-a-million-videos.md): I migrated close to a million videos from one hosting provider to another at TV2 Regionerne, and built a transcoding platform to re-encode archive video for cost savings. All without a single broken embed. - [Election Night: 90,000 Requests Per Second](https://cbox.dk/blog/election-night-90000-requests-per-second.md): The 2021 regional election in Denmark pushed the TV2 Regionerne websites to 3 billion requests in a single day. Here is how the infrastructure held up. - [Leaving EasyInspect](https://cbox.dk/blog/closing-easyinspect.md): After five years, I sold my share and left EasyInspect. The technology worked. The timing did not. Everyone wanted drone inspection, but nobody's existing workflows could absorb it yet. My vision for the product had diverged from where it was heading. - [UAV Commercial Show London](https://cbox.dk/blog/uav-commercial-show-london.md): EasyInspect at the Denmark Pavilion in London. Showing Inspection Cloud to European drone operators and meeting clients like SBB, Vejdirektoratet, and Sund & Bælt. - [EasyInspect at Commercial UAV Expo](https://cbox.dk/blog/easyinspect-at-commercial-uav-expo.md): Showing Inspection Cloud at Commercial UAV Expo in Las Vegas. Booth #132, good conversations about scaling drone inspection workflows for enterprise clients. - [Launching Inspection Cloud at TUS Nordics](https://cbox.dk/blog/launching-inspection-cloud-at-tus-nordics.md): After two years of building in stealth, I finally showed Inspection Cloud publicly at TUS Nordics in Odense. Innovation Fund Denmark backed us with a Grand Solutions grant. The response was overwhelming. - [Laracon EU 2017: First Time](https://cbox.dk/blog/laracon-eu-2017-first-time.md): I took two developers from EasyInspect to Laracon EU in Amsterdam. First Laracon. First time meeting Taylor. We were building drone inspection software on Laravel and it felt like the right community to be part of. - [Denmark's First Approved Drone School](https://cbox.dk/blog/denmarks-first-drone-school.md): MyDroneAcademy became Denmark's first approved drone school on August 31, 2016. The first class of 8 pilots took their theory exam on September 3rd at Amager Fælled. We went on to train over half of all Danish drone pilots. - [Running Denmark's Largest TeamSpeak Hosting](https://cbox.dk/blog/teamspeak-hosting-at-scale.md): From 2013 to 2016 I ran what was probably Denmark's largest TeamSpeak 3 server hosting through Cbox. 1.5 kr per slot, 99.99% uptime, DDoS protection, instant provisioning. Then Discord happened. - [Eventmanager: Accreditation for Festivals](https://cbox.dk/blog/eventmanager-festival-accreditation.md): Up to 40% of festival visitors are accredited guests who need different handling than ticket holders. I built Eventmanager for Ringsted Festival to manage the complexity. - [Talk IP and Flexfone: Building a Business Telephony Provider](https://cbox.dk/blog/talk-ip-business-telephony.md): I co-founded Talk IP with Thomas Fest. We resold Flexfone with our own SIP trunks, custom software, and QoS internet for Danish businesses. - [Building Broker Telephony for Danske Bank](https://cbox.dk/blog/broker-telephony-for-danske-bank.md): I built secure broker telephony systems for Danske Bank with direct secondary lines, logging, and high-security requirements. Implementations in London, Stockholm, and New York. - [Building an ERP System in Arezzo](https://cbox.dk/blog/bacio-italy-erp-from-arezzo.md): I spent months in Tuscany building a web-based ERP/CRM for Italian jewelry manufacturer Bacio Italy. Supply chain, stock, dealers, orders, production. Then they went bankrupt. - [GoIP: Running IP Telephony for 100,000 Customers](https://cbox.dk/blog/goip-100000-customers-ip-telephony.md): As telephony specialist at Debitel Danmark, I ran GoIP - one of Denmark's largest IP telephony providers. 100,000+ private customers, Asterisk, and a migration to Centile that could not fail. - [Hosting Unity's First Public Release](https://cbox.dk/blog/hosting-unitys-first-release.md): Before Unity became a multi-billion dollar game engine, they were a small team in Copenhagen. Cbox hosted their first public release. - [100,000 Websites on One Linux Server](https://cbox.dk/blog/hotserv-100000-websites-one-server.md): Hotserv.dk was a free web hosting platform I built when hosting was still expensive. Custom-compiled Apache, PHP, and MySQL on a single server running 100,000+ sites. - [Heste-Nettet: Building Denmark's Largest Horse Community](https://cbox.dk/blog/heste-nettet-denmarks-horse-community.md): I built and hosted Heste-Nettet, one of Denmark's biggest online portals. Forum, marketplace, live video streaming with DRM, and a SAML2 identity provider. We won the FDIH E-handelsprisen. - [UMCM: Cutting International Call Costs with SIP](https://cbox.dk/blog/umcm-callback-engine.md): I built an intelligent callback engine that routes calls through the cheapest carriers via SIP trunks. Used by DSV, Impregilo, and Ferrero.