Skip to content

Releases: devuni-cz/notifier-agent

v1.8.0

Choose a tag to compare

@github-actions github-actions released this 05 Aug 14:31

Added

  • The restore workflow is now discoverable end to end (#27). The notifier:database-restore / notifier:storage-restore commands have existed since 1.7.0, but nothing surfaced them: the README never mentioned them and a fresh install had no NOTIFIER_RESTORE_TOKEN, so restore was silently disabled until the operator discovered it from a runtime error.
    • notifier:install now 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:check gained 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 (--force on 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: --force now 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(), not text()) and rejects the backup code/password pasted by mistake — and notifier:check warns 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

Choose a tag to compare

@github-actions github-actions released this 04 Aug 22:24

Security

  • Bump guzzlehttp/guzzle to ^7.15.2 to 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's Illuminate\Support\Carbon::plus() override is fatally incompatible with carbon 3.12+ (which added a ?bool $overflow parameter), and the protective pin lived only in this repository's CI matrix — so CI stayed green while a client site running composer update on Laravel 12 could pull carbon 3.12 and crash on boot. The pin is now a composer conflict (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>.zip into storage/app/private with 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_at success 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. ProcessBackupJob ran 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 when NOTIFIER_BACKUP_PASSWORD was 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/public holds 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. .gitkeep also joined .gitignore in 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 from v1.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

Choose a tag to compare

@github-actions github-actions released this 27 Jul 01:19

Security

  • The backup trigger no longer allows a resource-exhaustion flood. The inbound POST {prefix}/backup runs the full dump + zip synchronously in the PHP-FPM worker on the default sync connection, 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 a Cache::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 in finally and auto-expiring after 900s so a crashed worker cannot wedge the type — and ProcessBackupJob is now ShouldBeUnique per 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

Choose a tag to compare

@github-actions github-actions released this 25 Jul 20:31

Security

  • Bump guzzlehttp/guzzle to ^7.15.1 to clear nine dependency advisories. The previous ^7.11.1 floor still resolved versions affected by seven Guzzle advisories (URI fragments leaking into Referer on redirect, host-only cookie scope not preserved, unbounded response cookies, Proxy-Authorization sent 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, two guzzlehttp/psr7 advisories (host confusion via weak URI host validation, CRLF injection in start-line serialization). ^7.15.1 pulls a fixed Guzzle which in turn requires guzzlehttp/psr7 >= 2.13, so composer audit now reports no advisories. No code changes — the agent's HTTP surface is unchanged.

Full Changelog: v1.7.0...v1.7.1

v1.7.0

Choose a tag to compare

@github-actions github-actions released this 23 Jul 11:15

Added

  • Restore commands: notifier:database-restore and notifier: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 separate NOTIFIER_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.backups is now a real toggle. The heartbeat manifest has always advertised a backups flag, but the key it reads was never defined in config/notifier.php, so it was hard-wired to true and could not be turned off. The key now exists and is driven by NOTIFIER_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. AnnouncementsService fell back to false for features.announcements and 60 for announcements.failure_cache_ttl, while config/notifier.php ships true and 300. 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 until NOTIFIER_URL is set) and cache a failed fetch for 5 minutes instead of 1.

Removed

  • Dead code in BackupTypeEnum. values() and validationRule() had no callers — request validation goes through Rule::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.md corrected; TODO.md refreshed 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

Full Changelog: v1.6.2...v1.7.0

v1.6.2

Choose a tag to compare

@github-actions github-actions released this 18 Jun 19:52

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 1 with a readable message via the shared backup flow instead of surfacing an uncaught stack trace.
  • CLI output overhauled. notifier:check, the backup commands, notifier:heartbeat and notifier:install now share one pass/warn/fail report vocabulary with a tri-state RESULT summary; notifier:install masks 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

Choose a tag to compare

@ludwigtomas ludwigtomas released this 17 Jun 22:18

Security

  • Hardened the v1.6.0 correlation logging against injection. The request-id validation regex now uses the /D anchor so a trailing newline can no longer slip through withRequestId() / the response-field guard, and the server-supplied upload status is control-character-stripped and length-capped before it is logged (the one server string the v1.6.0 work left raw).

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 17 Jun 13:50

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 single grep <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 structured error_id / request_id from the response (body or X-Request-Id header) 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_number and status for faster diagnosis.

Changed

  • Upload failures are now logged at error level instead of emergency — 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

Choose a tag to compare

@ludwigtomas ludwigtomas released this 16 Jun 22:49

Added

  • Replay protection — the agent signs every outbound request with X-Notifier-Timestamp, X-Notifier-Nonce and X-Notifier-Signature (HMAC-SHA256 over timestamp\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 to NOTIFIER_BACKUP_CODE for 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

Choose a tag to compare

@github-actions github-actions released this 15 Jun 00:11
6f10026

Added

  • Configurable cap on rendered announcement banners — notifier.announcements.max_visible (env NOTIFIER_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 via AnnouncementsService::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