Repository navigation
Releases: devuni-cz/notifier-agent
Releases · devuni-cz/notifier-agent
Release list
v1.8.0
Added
- The restore workflow is now discoverable end to end (#27). The
notifier:database-restore/notifier:storage-restorecommands have existed since 1.7.0, but nothing surfaced them: the README never mentioned them and a fresh install had noNOTIFIER_RESTORE_TOKEN, so restore was silently disabled until the operator discovered it from a runtime error.notifier:installnow offers to configure the restore token (issued per repository on the control plane) and to generate a dedicated inbound trigger secret. Both prompts default to skip, so the classic three-value install is unchanged — and the trigger-secret prompt warns that the control plane must be configured with the same value before remote triggers work again.notifier:checkgained a "restore & trigger credentials" section: it warns when the restore token is missing (restore commands disabled) and reports whether the inbound trigger runs in single-secret mode (backup code doubling as trigger secret) or with a dedicated secret.- The README documents the restore commands and a "Bootstrapping a fresh deployment" guide: deploy → carry over the original
NOTIFIER_BACKUP_PASSWORD(archives are encrypted with it — a freshly generated password cannot decrypt them) + set the restore token → restore both backups (--forceon production) — and explicitly explains that backing up a freshly migrated, near-empty database is expected to be rejected by the control plane's "Backup too small" threshold, so restore comes first and backups start once there is data. - The installer is safe to re-run:
--forcenow prefills the current values and asks to keep the existing backup password (default yes), so adding a restore token can no longer accidentally rotate the password that encrypts every already-uploaded archive. The restore token prompt is non-echoing (password(), nottext()) and rejects the backup code/password pasted by mistake — andnotifier:checkwarns instead of passing when the restore token equals the backup code, so a mixed-up credential cannot hide behind a green check.
What's Changed
- ci(deps): bump the actions group across 1 directory with 2 updates by @dependabot[bot] in #25
- feat: make the restore workflow discoverable (install, check, README) by @ludwigtomas in #28
Full Changelog: v1.7.3...v1.8.0
v1.7.3
Security
- Bump
guzzlehttp/guzzleto^7.15.2to clear two fresh advisories (reported 2026-08-03): CVE-2026-69246 (high — a noncanonical host can bypass host-based checks; directly relevant to the agent's trusted-status-URL origin guard) and CVE-2026-69245 (medium — a noncanonical cookie domain keeps subdomain scope). No code changes.
Fixed
- A Laravel 12 client install can no longer resolve an incompatible
nesbot/carbon. Laravel 12.62'sIlluminate\Support\Carbon::plus()override is fatally incompatible with carbon3.12+(which added a?bool $overflowparameter), and the protective pin lived only in this repository's CI matrix — so CI stayed green while a client site runningcomposer updateon Laravel 12 could pull carbon 3.12 and crash on boot. The pin is now a composerconflict(nesbot/carbon >= 3.12.0), so every install resolves carbon 3.11.x until Laravel 12 ships a fix. Laravel 13 accepts carbon^3.8.4, so Laravel 13 installs keep working — they are merely held back from 3.12+ until the conflict is lifted. - Concurrent backup runs can no longer clobber each other's archive. Every backup wrote
backup-<timestamp>.zipintostorage/app/privatewith one-second resolution, and concurrent runs are real: a database and a storage backup may run at once (the trigger lock is per type), and a scheduled command can meet a trigger-driven run of the same type (the command path takes no lock). The CLI zip creator deletes a pre-existing target as "stale", so a same-second neighbour could delete or overwrite an archive mid-run. Archive names now carry a type prefix and a random per-run suffix (backup-database-<timestamp>-<random>.zip/backup-storage-…), so no two runs can ever share a path; the database dump and its ZIP also share one timestamp instead of taking two. - Trigger-driven backups now stamp the heartbeat timestamps. The
last_database_backup_at/last_storage_backup_atsuccess stamp was written only by the artisan commands, so a backup started via the inbound trigger (sync or queued) shipped fine but the heartbeat manifest kept reporting the old timestamp — the control plane could alarm on backups that were in fact running nightly. The stamp now lives in the upload services, at the one point every successful upload passes through, covering the command, sync-trigger and queued paths alike. - A queued backup job re-checks the environment before running.
ProcessBackupJobran without any environment validation — the trigger route's middleware checks it, but a job dispatched directly from host code (or an env change between dispatch and a queued run) could produce an unencrypted database upload whenNOTIFIER_BACKUP_PASSWORDwas missing. The job now fails fast with the missing variable names, matching the command path's refusal to run. - An effectively-empty storage backup skips cleanly instead of failing against the server. A site whose
storage/app/publicholds only a placeholder (.gitkeep) produced a ~200 B archive: the agent's own "nothing to back up" floor was just 100 B, so the archive shipped — and the control plane rejected it with "Backup too small (< 102 400 B)", turning every scheduled run into a hard failure. The agent floor is now configurable (NOTIFIER_MIN_STORAGE_BACKUP_BYTES) and defaults to the server's 102 400 B, so a sub-threshold archive is treated like an empty source: warn + skip (exit 0), no upload, no heartbeat stamp..gitkeepalso joined.gitignorein the default storage exclusions, so a placeholder-only directory is recognized as empty outright. Database backups are unaffected — a database dump is never legitimately empty, so it always ships. - CHANGELOG footer links repaired. The
[1.7.1]and[1.7.2]sections had no link definitions and[Unreleased]still compared fromv1.7.0.
What's Changed
- fix: collision-proof backup archives, heartbeat stamps on every path, carbon/guzzle guards by @ludwigtomas in #26
Full Changelog: v1.7.2...v1.7.3
v1.7.2
Security
- The backup trigger no longer allows a resource-exhaustion flood. The inbound
POST {prefix}/backupruns the full dump + zip synchronously in the PHP-FPM worker on the defaultsyncconnection, and its route throttle is per-IP (trivially rotated), so a holder of the trigger secret could stack unbounded concurrent backups and take the client site offline. Each backup type is now serialized with aCache::lock— a second same-type trigger returns 429 instead of piling another dump onto the worker (a database and a storage backup may still run at once), released infinallyand auto-expiring after 900s so a crashed worker cannot wedge the type — andProcessBackupJobis nowShouldBeUniqueper type, so a flood on a queued connection collapses to one in-flight job per type instead of an unbounded backlog.
What's Changed
- security: harden the backup trigger against a resource-exhaustion flood by @ludwigtomas in #23
Full Changelog: v1.7.1...v1.7.2
v1.7.1
Security
- Bump
guzzlehttp/guzzleto^7.15.1to clear nine dependency advisories. The previous^7.11.1floor still resolved versions affected by seven Guzzle advisories (URI fragments leaking intoRefereron redirect, host-only cookie scope not preserved, unbounded response cookies,Proxy-Authorizationsent to origin servers, dot-only cookie domains matching all hosts, a silent HTTPS→cleartext proxy downgrade, and CVE-2026-59883 cookie disclosure/injection via IP-address domains) and, transitively, twoguzzlehttp/psr7advisories (host confusion via weak URI host validation, CRLF injection in start-line serialization).^7.15.1pulls a fixed Guzzle which in turn requiresguzzlehttp/psr7 >= 2.13, socomposer auditnow reports no advisories. No code changes — the agent's HTTP surface is unchanged.
Full Changelog: v1.7.0...v1.7.1
v1.7.0
Added
- Restore commands:
notifier:database-restoreandnotifier:storage-restore. Pull this site's own backups back down from the control plane and apply them — for bootstrapping a new node, disaster recovery, or refreshing staging. Restore is authenticated with a separateNOTIFIER_RESTORE_TOKEN, never the backup code, so leaking the credential that uploads backups cannot also be used to download them; an install with no restore token configured cannot restore at all. Both commands support--list,--dry-run,--id=(defaults to the newest backup) and, in production, require--force; the database restore additionally asks you to type the database name to confirm and dumps the current database to a pre-restore snapshot first (skip with--no-snapshot). Storage restore is additive — it overwrites files with matching paths but never deletes anything the backup predates. - Restore is hardened against a hostile or compromised control plane. A downloaded archive is verified against the SHA-256 checksum the server recorded at upload time (the listing copy and the response header must agree, and both must match the bytes on disk) before anything is applied. The extractor refuses an archive whose entries are not encrypted, caps the extracted size to defuse zip bombs, and rejects path-traversal (zip-slip) and absolute-path entries. A restored SQL dump is scanned for client-side directives (
\!,system,source,\copy…) and refused before a single byte reaches the database client, closing an RCE path through a poisoned dump. features.backupsis now a real toggle. The heartbeat manifest has always advertised abackupsflag, but the key it reads was never defined inconfig/notifier.php, so it was hard-wired totrueand could not be turned off. The key now exists and is driven byNOTIFIER_BACKUPS_ENABLED, letting a site that intentionally does not back up say so to the control plane instead of appearing to be a silent failure.
Changed
- Announcement config fallbacks now match the shipped defaults.
AnnouncementsServicefell back tofalseforfeatures.announcementsand60forannouncements.failure_cache_ttl, whileconfig/notifier.phpshipstrueand300. The in-code fallbacks were aligned with the config. These fallbacks only apply when the key is absent entirely — a site whose published config predates these keys will now default announcements to on (still a no-op untilNOTIFIER_URLis set) and cache a failed fetch for 5 minutes instead of 1.
Removed
- Dead code in
BackupTypeEnum.values()andvalidationRule()had no callers — request validation goes throughRule::enum— and were removed.
Internal
- CI matrix now also covers PHP 8.5 + Laravel 12, the one supported combination that was untested.
- CHANGELOG compare links, README badge and
VERSION_MANAGEMENT.mdcorrected;TODO.mdrefreshed against the actual state of the code.
What's Changed
- ci(deps): bump the actions group across 1 directory with 2 updates by @dependabot[bot] in #21
- feat: notifier:database-restore & notifier:storage-restore by @ludwigtomas in #22
New Contributors
- @dependabot[bot] made their first contribution in #21
Full Changelog: v1.6.2...v1.7.0
v1.6.2
Fixed
- A failed or empty storage backup can no longer report success. After building the archive the agent now checks its size; a
< 100 B(or unreadable) file is treated as "nothing to back up" — it logs a warning, removes the stub file and skips the upload instead of silently reporting a successful run. The heartbeat success timestamp is likewise stamped only after a real upload completes, so a creation failure or an empty source can no longer leave a green heartbeat behind a backup that never actually shipped.
Changed
- Backup commands fail cleanly. Archive-creation errors now exit
1with a readable message via the shared backup flow instead of surfacing an uncaught stack trace. - CLI output overhauled.
notifier:check, the backup commands,notifier:heartbeatandnotifier:installnow share one pass/warn/fail report vocabulary with a tri-stateRESULTsummary;notifier:installmasks the backup code/password in its recap (presence and length only, never plaintext); a non-writable storage path prints an actionable chown/chmod hint; and logo/badge/ANSI decoration is suppressed when the output is not a TTY, so cron and redirected logs stay clean.
Full Changelog: v1.6.1...v1.6.2
v1.6.1
Security
- Hardened the v1.6.0 correlation logging against injection. The request-id validation regex now uses the
/Danchor so a trailing newline can no longer slip throughwithRequestId()/ the response-field guard, and the server-supplied uploadstatusis control-character-stripped and length-capped before it is logged (the one server string the v1.6.0 work left raw).
v1.6.0
Added
- End-to-end request correlation — every HTTP call of a single backup run (init → chunks → finalize → status poll) now carries one shared
X-Request-Id, so a run can be matched across the agent log and the control-plane log with a singlegrep <request_id>. Additive and backward-compatible: an un-upgraded server simply ignores the header. - Server error surfacing — when the control plane returns a
5xx, the agent reads the structurederror_id/request_idfrom the response (body orX-Request-Idheader) and appends them to the failure message and logs (e.g.Failed to initialize upload: HTTP 500 - Internal Server Error (error_id=…, request_id=…)), turning an opaque "Server Error" into a pointer straight to the matching server-side exception. All server-supplied fields (the message and the ids) are sanitized — control characters stripped, length capped — before they reach a log line; the token is never logged. - Structured upload logs — chunked-upload log entries now include
stage(init_upload/send_chunk/finalize/poll_status),upload_id,attempt,chunk_numberandstatusfor faster diagnosis.
Changed
- Upload failures are now logged at
errorlevel instead ofemergency— a transient upload failure is no longer the highest severity, so genuine emergencies stand out.
Full Changelog: v1.5.0...v1.6.0
v1.5.0
Added
- Replay protection — the agent signs every outbound request with
X-Notifier-Timestamp,X-Notifier-NonceandX-Notifier-Signature(HMAC-SHA256 overtimestamp\nnonce, keyed by the SHA-256 hash of the token). Additive and backward-compatible: the server verifies the signature only when present, so un-upgraded servers/agents are unaffected. NOTIFIER_TRIGGER_SECRET— inbound server→client backup triggers now authenticate against a dedicated trigger secret, with a runtime fallback toNOTIFIER_BACKUP_CODEfor single-secret installs. This pairs with the control plane storing inbound tokens hashed at rest.- Strong backup ZIP password policy in
notifier:install— minimum 16 characters and at least 6 distinct characters; a strong password is auto-generated by default.
Security
- Hardened the release workflow (
release.yml) against unauthorized publishes (tag/branch and permission guards).
Fixed
- Windows ZIP entry-path separator bug in
PhpZipCreator— archive entry names are normalized to forward slashes so archives built on a Windows client are well-formed and exclusion matching works.
v1.4.1
Added
- Configurable cap on rendered announcement banners —
notifier.announcements.max_visible(envNOTIFIER_ANNOUNCEMENTS_MAX_VISIBLE, default 5). Each render location (the Filament render-hook banner and the<x-notifier-announcements-notice />Blade component) shows the top-N priority-ordered announcements plus a muted+ N dalších oznámeníoverflow line;0= unlimited. SPA/custom hosts viaAnnouncementsService::customAnnouncements()still receive all announcements and decide their own display.
Changed
- Announcement validity-window separator is now a plain hyphen
-instead of an en-dash–(avoids mojibake/odd rendering).
What's Changed
- feat(announcements): cap rendered banners (max_visible, default 5) + plain-hyphen separator by @ludwigtomas in #15
Full Changelog: v1.4.0...v1.4.1