Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,25 @@ When generating code for this repository:
## Project Structure

```
webseer/ # Repository root (install to plugins/webseer/ in Cacti)
├── classes/ # Supporting PHP classes (cURL, mxlookup)
├── includes/ # Library/helper files, require_once'd from the entry points
│ ├── database.php # Schema management: table defs + create/upgrade/drop helpers
│ ├── functions.php # Shared plugin functions
│ ├── arrays.php # Shared option/label arrays
│ └── constants.php # Shared constants
├── locales/ # Internationalization files
├── tests/ # Test suite
├── ca-bundle.crt # CA bundle for HTTPS endpoint verification
├── poller_webseer.php # Background poller entry point (CLI)
├── remote.php # Remote poller support endpoint
├── webseer.php # Main viewer/administration UI
├── webseer_process.php # Check execution logic
├── webseer_proxies.php # Proxy administration
├── webseer_servers.php # Monitored server/service administration
├── INFO # Plugin metadata (name, version, compat)
webseer/ # Repository root (install to plugins/webseer/ in Cacti)
├── classes/ # Supporting PHP classes (cURL, mxlookup)
├── includes/ # Library/helper files, require_once'd from the entry points
│ ├── database.php # Schema management: table defs + create/upgrade/drop helpers
│ ├── functions.php # Shared plugin functions
│ ├── arrays.php # Shared option/label arrays
│ └── constants.php # Shared constants
├── locales/ # Internationalization files
├── tests/ # Test suite
├── ca-bundle.crt # CA bundle for HTTPS endpoint verification
├── poller_webseer.php # Background poller entry point (CLI)
├── remote.php # Remote poller support endpoint
├── webseer.php # Main viewer/administration UI
├── webseer_process.php # Check execution logic
├── webseer_proxies.php # Proxy administration
├── webseer_servers.php # Monitored server/service administration
├── INFO # Plugin metadata (name, version, compat)
├── README.md
└── setup.php # Plugin install/uninstall/upgrade hooks
└── setup.php # Plugin install/uninstall/upgrade hooks
```

## Naming Conventions
Expand Down Expand Up @@ -219,3 +219,7 @@ existing code or adding new code, not just in dedicated cleanup passes:
line, `@param` lines, a blank comment line, then `@return`. Infer parameter/return types from
actual usage; don't change the function's real type-hints in the same pass (let static analysis
flag mismatches separately). Skip vendored third-party library files.

## File manifest & upgrade pruning

The plugin ships a root `manifest.json` with three arrays: `tombstones` (files/directories older versions shipped that have since moved or been removed), `expected` (the top-level files and directories that ship today, directories written with a trailing `/`), and `whitelist` (paths holding user data that must never be touched). Keep `expected` current: CI runs `tests/bin/validate-manifest.php`, which fails on any drift between `expected` and the real top-level tree (it ignores `tests/`, `phpunit.xml`, `.git*`, `.md*`, and whitelisted paths). Custom customer CSS/theme files belong in `expected`, and stylesheets live in `css/` (not `themes/`). On upgrade, `webseer_prune_files()` deletes the tombstoned paths, the dev-only `tests/` tree, and the `phpunit.xml` test config, leaves `whitelist`, `.git*`, and `.md*` alone, and logs (without removing) any top-level entry the manifest does not account for. As a safety measure it refuses any tombstone that resolves outside the plugin directory (a tampered manifest.json) and logs a warning for any file or directory it cannot remove. When you move or delete a shipped file, add its old path to `tombstones` and update `expected` in the same change.
3 changes: 3 additions & 0 deletions .github/workflows/plugin-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ jobs:
- name: Check PHP version
run: php -v

- name: Validate plugin manifest (expected-file drift)
run: php cacti/plugins/webseer/tests/bin/validate-manifest.php

- name: Run apt-get update
run: sudo apt-get update

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

--- 3.3 ---

* compat: Lower the minimum Cacti compatibility floor from 1.2.32 to 1.2.29
* issue: Re-introduce the `plugin_webseer_contacts` `user_id_type` UNIQUE key (on `user_id`, `type`) that Cacti 1.2.29-1.2.31's `db_update_table()` silently dropped - those releases rebuild a table's indexes only from its `keys` definition and ignore `unique_keys`. The repair runs automatically on upgrade when running one of those releases and is a no-op on 1.2.32+. WARNING: if duplicate `(user_id, type)` contact rows were inserted while the constraint was absent, re-adding the key fails; the upgrade logs the affected table and leaves the recorded version unchanged so it retries on the next request. To recover, de-duplicate `plugin_webseer_contacts` (keep a single row per `user_id`/`type`) and re-run the upgrade. Sites already upgraded to 1.2.32+ that lost the key on an earlier 1.2.29-1.2.31 run are not auto-repaired
* feature: Add a root `manifest.json` and an upgrade-time file prune that removes dev-only bundled files (e.g. `tests/`) on a version change, refuses any path resolving outside the plugin directory, and logs anything it cannot remove
* refactor: Move all schema management into includes/database.php (the thold model) and manage the seven plugin_webseer_* tables through Cacti's plugin table API - create with api_plugin_db_table_create() and refresh existing tables with db_update_table() from a single shared definition, replacing the raw CREATE TABLE/version-gated ALTER TABLE migrations (the historical plugin_webseer_url_log -> plugin_webseer_urls_log rename is kept as a guarded pre-step); the upgrade path now also updates the full plugin_config row. Also switches every file inclusion from include/include_once to require/require_once
* dev: Measure CI coverage with xdebug instead of pcov so the plugin's own sources are instrumented (pcov auto-scopes to the Composer root and skipped cacti/plugins/, leaving the patch-coverage gate with nothing to measure)
* dev: Enforce patch coverage of changed lines in CI and remove the inert COMPOSER_ROOT_VERSION env from the Pest step
Expand Down
2 changes: 1 addition & 1 deletion INFO
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ longname = Service Monitor
author = The Cacti Group
email = jimmy@sqmail.org
homepage = http://cactiusers.org
compat = 1.2.32
compat = 1.2.29
Comment thread
TheWitness marked this conversation as resolved.
capabilities = online_view:1, online_mgmt:1, offline_view:0, offline_mgmt:0, remote_collect:1
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ this, goto Cacti's Plugin Management page, Install and Enable the webseer. Once
this is complete, you can grant users permission to create service checks for
various Web Sites and Services.

## Upgrading

`poller_webseer.php` runs as a long-lived background process, so a running copy
keeps the old code in memory across a plugin upgrade. After upgrading the plugin
files, restart the webseer background process (stop the running
`poller_webseer.php`; Cacti's poller relaunches it on the next cycle) so it
picks up the new code. On upgrade the plugin also prunes its own bundled
development-only files (for example the `tests/` directory) from the installed
tree.

## Bugs and Feature Enhancements

Bug and feature enhancements for the webseer plugin are handled in GitHub. If
Expand Down
57 changes: 55 additions & 2 deletions includes/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,11 @@ function plugin_webseer_setup_table() {
* is applied as a guarded pre-step first. Called from
* plugin_webseer_upgrade() when the stored version changes.
*
* @return void
* @return bool True if the refresh (including any compensating UNIQUE-key
* re-add) fully succeeded; false if a unique key could not be
* restored, so the caller can defer version bookkeeping.
*/
function webseer_upgrade_tables() {
function webseer_upgrade_tables(): bool {
// db_update_table() can not rename, so carry the historical URL-log
// table rename (and its data) forward before the schema refresh.
if (db_table_exists('plugin_webseer_url_log') && !db_table_exists('plugin_webseer_urls_log')) {
Expand All @@ -296,6 +298,57 @@ function webseer_upgrade_tables() {
api_plugin_db_table_create('webseer', $table, $data);
}
}

return webseer_reintroduce_unique_keys();
}

/**
* Cacti 1.2.29 through 1.2.31 shipped a db_update_table() that silently
* dropped UNIQUE keys declared via a table definition's 'unique_keys' (it
* rebuilds indexes only from 'keys'). Re-add this plugin's unique keys when
* running on one of those releases; 1.2.32+ preserves them, so this is a
* no-op there. Uses Cacti's db_add_index() (available since 1.2.29) and
* skips any key that already exists. When a key cannot be re-added - almost
* always because duplicate rows were inserted while the constraint was
* absent - the failure is logged with remediation steps and reported to the
* caller so version bookkeeping can be deferred and the repair retried.
*
* @return bool True if every applicable unique key is present (or the gate
* does not apply); false if any key could not be re-added.
*/
function webseer_reintroduce_unique_keys(): bool {
$running = trim((string) db_fetch_cell('SELECT cacti FROM version LIMIT 1'));

if ($running === '' ||
!cacti_version_compare($running, '1.2.29', '>=') ||
!cacti_version_compare($running, '1.2.32', '<')) {
return true;
}

$unique_keys = [
['table' => 'plugin_webseer_contacts', 'name' => 'user_id_type', 'columns' => ['user_id', 'type']],
];

$ok = true;

foreach ($unique_keys as $uk) {
if (db_index_exists($uk['table'], $uk['name'])) {
continue;
}

if (!db_add_index($uk['table'], 'UNIQUE', $uk['name'], $uk['columns'])) {
$ok = false;

cacti_log(sprintf('ERROR: webseer could not re-add UNIQUE key `%s` on `%s` (%s). '
. 'This happens only when duplicate (%s) rows were inserted while the constraint '
. 'was absent on Cacti 1.2.29-1.2.31; de-duplicate the table, then re-run the upgrade '
. 'to restore it.',
$uk['name'], $uk['table'], implode(', ', $uk['columns']), implode(', ', $uk['columns'])),
false, 'WEBSEER');
}
}

return $ok;
}

/**
Expand Down
24 changes: 24 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"tombstones": [],
"expected": [
"CHANGELOG.md",
"INFO",
"LICENSE",
"README.md",
"ca-bundle.crt",
"classes/",
"images/",
"includes/",
"index.php",
"locales/",
"manifest.json",
"poller_webseer.php",
"remote.php",
"setup.php",
"webseer.php",
"webseer_process.php",
"webseer_proxies.php",
"webseer_servers.php"
],
"whitelist": []
}
Loading
Loading