blax-software/laravel-workkit

Laravel collection of helpers and utilities to reduce redundant code over multiple projects.

Maintainers

Package info

github.com/blax-software/laravel-workkit

Homepage

pkg:composer/blax-software/laravel-workkit

Transparency log

Statistics

Installs: 693

Dependents: 3

Suggesters: 0

Stars: 1

Open Issues: 0

v1.1.1 2026-04-29 12:26 UTC

This package is auto-updated.

Last update: 2026-07-28 09:22:12 UTC


README

Blax Software OSS

Laravel Workkit

PHP Version Laravel License

A Laravel collection of helpers and utilities to reduce redundant code over multiple projects.

Features

  • ๐Ÿ’พ Streaming DB backups โ€” mysqldump | xz | openssl in one pipe, APP_KEY-encrypted, zero DB bytes held in PHP memory (multi-GB safe)
  • โ™ป๏ธ Safe restores โ€” --fresh wipes a dirty/partial schema before importing, after taking + verifying a recovery snapshot; failed imports never leave you stranded
  • ๐Ÿ”Ž Backup verification โ€” workkit:db:verify proves a backup decrypts + is a complete xz stream without a database, so "is it corrupt?" gets a real answer
  • ๐Ÿงน Retention pruning โ€” age-based cleanup that always keeps the N newest, so prune can never delete your last recovery point
  • ๐Ÿ“Š Stats overview โ€” workkit:stats prints per-model row counts, the largest tables, total DB size + a queue/backup summary
  • ๐Ÿฉบ Queue health โ€” workkit:queue:health reports per-queue depth/age/failed jobs and exits non-zero on breach (cron-friendly)
  • ๐Ÿ“„ Variable pagination โ€” a #[VariablePaginatable] attribute + request()->perPage() macro for per-route, user-overridable page sizes
  • ๐Ÿงฉ Reusable middleware & traits โ€” bearer-token auth, force-JSON responses, HasExpiration, HasMeta, HasMetaTranslation, and small service helpers

Quick Start

composer require blax-software/laravel-workkit
php artisan workkit:db:backup            # storage/backups/db_mysql_<timestamp>.sql.xz.enc
php artisan workkit:db:verify            # is the newest backup intact?
php artisan workkit:db:restore --fresh   # wipe + restore the newest backup

Backups are encrypted with a key derived from your app's APP_KEY; a backup is restorable only by a deployment that knows the same APP_KEY.

Database Backups

A streaming, compressed, encrypted MySQL backup/restore suite. The dump, compression and encryption happen in a single shell pipe, so PHP never holds the database content in memory regardless of dump size.

Commands

Command What it does
workkit:db:backup Stream mysqldump โ†’ xz โ†’ openssl into storage/backups, write a .meta.json sidecar (size, sha256, params).
workkit:db:restore Stream openssl โ†’ xz โ†’ mysql. Verifies the source first; --fresh wipes the target after a verified safety snapshot.
workkit:db:verify Prove a backup decrypts and is a complete xz stream โ€” no database touched.
workkit:db:prune-backups Delete backups older than the retention window, always keeping the N newest.

Backup

php artisan workkit:db:backup                 # default connection
php artisan workkit:db:backup --connection=mysql --xz-level=6
php artisan workkit:db:backup --verify        # confirm the backup right after writing

Restore

# Newest restorable backup into the default connection (prompts unless --force)
php artisan workkit:db:restore

# A specific file
php artisan workkit:db:restore --file=db_mysql_2026-06-28_09-21-49.sql.xz.enc

# Wipe the target first โ€” the ONLY thing that fixes a restore failing with
# errno 150 / error 3780 against a dirty or partially-migrated schema.
php artisan workkit:db:restore --fresh

--fresh is deliberately careful, because dropping every table is irreversible:

  1. The source backup is verified before anything is touched โ€” a file that can't be decrypted never triggers a wipe.
  2. A pre-restore safety snapshot of the current database is taken and verified (skip with --no-safety-backup; the restore aborts if the snapshot can't be written, rather than wiping with no recovery point).
  3. Only then are all tables + views dropped โ€” via the same mysql client and database the import uses, so the wipe and the import provably hit the same server (a url DSN / unix_socket / read-write split can't make them diverge).
  4. If the import fails after the wipe, the command prints the exact workkit:db:restore --file=<snapshot> command to recover.

Interactively, --fresh asks you to type the database name to confirm; under --force it proceeds non-interactively for deploy scripts.

Why --fresh and not just FK checks? A leftover table with an incompatible column type makes MySQL raise errno 150 / error 3780 at CREATE TABLE even with FOREIGN_KEY_CHECKS=0 (which restores already set). The only fix is an empty target. If a restore fails this way, the command tells you to re-run with --fresh.

Verify

php artisan workkit:db:verify                 # newest backup
php artisan workkit:db:verify --file=db_mysql_2026-06-28_09-21-49.sql.xz.enc
php artisan workkit:db:verify --all           # every backup in the directory

Verify proves integrity (decrypts with this host's APP_KEY + a complete, non-truncated xz stream), flags a missing mysqldump completion marker (source-truncation), and compares the on-disk sha256 against the .meta.json sidecar. It does not prove restorability โ€” a valid backup of the wrong or empty database still verifies "OK".

Prune

php artisan workkit:db:prune-backups --dry-run
php artisan workkit:db:prune-backups --days=30 --keep-min=5

The --keep-min floor (default 5) is always kept regardless of age, so an aggressive --days can never leave you with zero backups. Schedule it:

Schedule::command('workkit:db:prune-backups')->daily();

Configuration

php artisan vendor:publish --tag=workkit-config
Key Env Default Purpose
backup.path WORKKIT_BACKUP_PATH storage/backups Where backups live
backup.retention_days WORKKIT_BACKUP_RETENTION_DAYS 30 Age cutoff for prune
backup.retention_min_keep WORKKIT_BACKUP_RETENTION_MIN_KEEP 5 Newest backups always kept
backup.xz_level WORKKIT_BACKUP_XZ_LEVEL 3 xz compression level (0โ€“9)

Requirements

The host needs mysqldump, mysql, xz, openssl and bash on PATH โ€” standard on any reasonable Linux server. A non-empty APP_KEY is required (backups are unrecoverable without the key that produced them).

Restoring without the package

The output is plain openssl enc -salt format, so any host with the same APP_KEY can restore it directly:

openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -pass env:WK_KEY \
  -in db_mysql_2026-06-28_09-21-49.sql.xz.enc | xz -d | mysql <database>
# WK_KEY = your APP_KEY with the "base64:" prefix stripped

Ops & Observability

Stats overview

php artisan workkit:stats                  # models + largest tables + DB size + queue/backup summary
php artisan workkit:stats --exact          # true COUNT(*) per table (heavier) instead of the estimate
php artisan workkit:stats --models --json  # just per-model counts, machine-readable
php artisan workkit:stats --tables --limit=30

Model row counts are estimated from information_schema on MySQL (instant); pass --exact for a real COUNT(*). Models are auto-discovered from app/Models โ€” override with config('workkit.stats.models') (an explicit FQCN list) or models_path. Table/DB sizes are MySQL-only and degrade to "n/a" elsewhere.

Queue health

php artisan workkit:queue:health                       # human table; exits 0 / non-zero
php artisan workkit:queue:health --json || notify-ops  # cron / monitoring probe
php artisan workkit:queue:health --max-age=10 --max-depth=500 --max-failed=50

For the database queue driver it reports per-queue pending / due / delayed / reserved counts and the oldest due job's age, plus failed jobs (total + last N hours). It flags a queue STALLED โ€” due work older than max_age_minutes with nothing reserved (the worker is probably down) โ€” and exits non-zero on any breach so it slots straight into cron or a health probe. Thresholds live under config('workkit.queue.*'), with per-queue depth overrides for intentionally-slow queues. Schedule it:

Schedule::command('workkit:queue:health')->everyFiveMinutes();

Changelog

See CHANGELOG.md.

Star History

Star History Chart