# +-------------------------------------------------------------------------+ # | Copyright (C) 2004-2026 The Cacti Group | # | | # | This program is free software; you can redistribute it and/or | # | modify it under the terms of the GNU General Public License | # | as published by the Free Software Foundation; either version 2 | # | of the License, or (at your option) any later version. | # | | # | This program is distributed in the hope that it will be useful, | # | but WITHOUT ANY WARRANTY; without even the implied warranty of | # | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | # | GNU General Public License for more details. | # +-------------------------------------------------------------------------+ # | Cacti: The Complete RRDtool-based Graphing Solution | # +-------------------------------------------------------------------------+ # | This code is designed, written, and maintained by the Cacti Group. See | # | about.php and/or the AUTHORS file for specific developer information. | # +-------------------------------------------------------------------------+ # | http://www.cacti.net/ | # +-------------------------------------------------------------------------+ name: Cacti Commit Audit on: push: branches: [develop, '1.2.x'] pull_request: branches: [develop, '1.2.x'] merge_group: permissions: contents: read concurrency: group: build-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: "COMPOSER_ALLOW_SUPERUSER": 1 jobs: javascript-contracts: name: JavaScript contracts runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false - name: Install Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 with: node-version: '22' - name: Install managed JavaScript and test contracts run: | set -euo pipefail npm ci --ignore-scripts npm run postinstall --ignore-scripts npm test npm run test:coverage # spelling: # name: Spell Check # runs-on: ubuntu-latest # steps: # - name: Checkout # uses: actions/checkout@v7.0.0 # - name: Install codespell # run: pip install codespell==2.4.1 # - name: Run codespell # run: codespell --config .codespell.cfg runtime-compatibility: name: PHP ${{ matrix.php }} locked runtime runs-on: ubuntu-24.04 timeout-minutes: 20 strategy: fail-fast: false matrix: php: ['8.3', '8.4', '8.5'] steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false - name: Setup locked PHP dependencies uses: ./.github/actions/setup-php-composer with: php-version: ${{ matrix.php }} extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring coverage: none dependency-mode: locked-runtime install-args: --no-dev --prefer-dist --no-progress --no-interaction - name: Verify actual platform requirements run: composer check-platform-reqs --no-dev - name: Smoke-test the production autoloader run: php -r "require 'include/vendor/autoload.php';" native-dependencies: name: PHP ${{ matrix.php }} native dependencies runs-on: ubuntu-24.04 timeout-minutes: 30 strategy: fail-fast: false matrix: php: ['8.3', '8.4', '8.5'] steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false - name: Setup Node uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 with: node-version: '22' cache: npm - name: Build managed JavaScript run: | set -euo pipefail npm ci --ignore-scripts npm run postinstall --ignore-scripts - name: Setup native PHP dependencies uses: ./.github/actions/setup-php-composer with: php-version: ${{ matrix.php }} extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring coverage: none dependency-mode: native cache-dependency-path: composer.json - name: Create native compatibility definition run: | set -euo pipefail cp composer.json composer.compat.json COMPOSER=composer.compat.json composer config platform.php "${{ matrix.php }}" - name: Resolve native dependencies env: COMPOSER: composer.compat.json run: | set -euo pipefail composer update --no-install --no-scripts --no-plugins --prefer-dist --no-progress --no-interaction composer install --no-scripts --prefer-dist --no-progress --no-interaction - name: Run unit tests run: include/vendor/bin/pest --configuration=tests/phpunit.xml --display-warnings --testsuite=Unit check: runs-on: ubuntu-24.04 timeout-minutes: 60 permissions: contents: read packages: read services: mysql: image: ghcr.io/cacti/ci-images/mariadb@sha256:2fdc84794931c0abde9ba6a8c9cb5a4635a02d851a103f0301a8b606dfc5cd98 credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} env: MYSQL_DATABASE: cacti MYSQL_HOST: 127.0.0.1 MYSQL_USER: cactiuser MYSQL_PASSWORD: cactiuser MYSQL_ROOT_PASSWORD: cactiroot ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 name: PHP 8.3 locked integration steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 persist-credentials: false - name: Setup Node uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 with: node-version: '22' cache: npm - name: Build managed JavaScript run: | set -euo pipefail npm ci --ignore-scripts npm run postinstall --ignore-scripts - name: Setup locked PHP 8.3 dependencies uses: ./.github/actions/setup-php-composer with: php-version: '8.3' extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring ini-values: post_max_size=256M, max_execution_time=60, date.timezone=America/New_York coverage: none dependency-mode: locked-quality install-args: --prefer-dist --no-progress --no-interaction - name: Check PHP version run: php -v - name: Prime fontconfig's cache run: sudo fc-cache - name: Validate composer.json and composer.lock run: composer validate --strict - name: Verify actual platform requirements run: composer check-platform-reqs - name: Run Linter on base code run: composer run-script lint working-directory: . - name: Run hardening pattern guard if: github.event_name == 'pull_request' run: composer run-script hardening-patterns -- --base="${{ github.event.pull_request.base.sha }}" --head=HEAD working-directory: . - name: Reject committed Composer dependencies run: composer run-script vendor-policy working-directory: . - name: Checking coding standards on base code run: composer run-script phpcsfixer working-directory: . - name: Run PHPStan at Level 6 on base code run: composer run-script phpstan working-directory: . - name: Run Pest Unit Tests run: composer run-script test working-directory: . - name: Checkout Thold Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/plugin_thold ref: 34c4a69b752b8eb0d73e90f1cd1c2a9b317a9d1b path: plugins/thold persist-credentials: false - name: Checkout Monitor Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/plugin_monitor ref: 11c7b6c6422d7ffaba9c2dcac25b729d9cb6ff12 path: plugins/monitor persist-credentials: false - name: Checkout MacTrack Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/plugin_mactrack ref: 408adb447cbb7b4ef4360bade646b8d926f7b293 path: plugins/mactrack persist-credentials: false - name: Checkout Intropage Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/plugin_intropage ref: f3a00ff7fb4a7f0c26b8910cd32c5c98e45da1e9 path: plugins/intropage persist-credentials: false - name: Checkout FlowView Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/plugin_flowview ref: ee37c8938c99bc49f6a2933f373865c65f51fba0 path: plugins/flowview persist-credentials: false - name: Checkout Audit Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/plugin_audit ref: 1d7be4f422a3831dcaa150747ad7314168b60605 path: plugins/audit persist-credentials: false - name: Checkout Syslog Plugin uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: Cacti/plugin_syslog ref: 82df21ba2acf38ec4052aa862d2b53c67287f311 path: plugins/syslog persist-credentials: false - name: Enable Syslog batch installation run: | set -euo pipefail # With syslog we have to enable batch installs using the lines below cd ${{ github.workspace }}/plugins/syslog cp -p config.php.dist config.php # shellcheck disable=SC2016 sed -i 's/\/\/\$syslog_install_options/\$syslog_install_options/g' config.php - name: Move plugin config files run: | set -euo pipefail cp -p ${{ github.workspace }}/plugins/flowview/config.php.dist ${{ github.workspace }}/plugins/flowview/config.php cp -p ${{ github.workspace }}/plugins/syslog/config.php.dist ${{ github.workspace }}/plugins/syslog/config.php sed -i 's/\/\/\$/\$/g' ${{ github.workspace }}/plugins/syslog/config.php - name: Run apt-get update run: sudo apt-get -y update - name: Install Apache and Tools run: sudo apt-get install apache2 snmp snmpd rrdtool fping libapache2-mod-php - name: Start SNMPD Agent and Test Agent run: | set -euo pipefail sudo systemctl start snmpd sudo snmpwalk -c public -v2c -On localhost .1.3.6.1.2.1.1 - name: Link Web Root and Grant the Web Group Access run: | set -euo pipefail sudo ln -s /home/runner/work/cacti/cacti /var/www/html sudo chgrp -R www-data "${{ github.workspace }}" sudo find "${{ github.workspace }}" -type d -exec chmod g+rwx {} + sudo find "${{ github.workspace }}" -type f -exec chmod g+rw {} + sudo chmod o+x /home sudo chmod o+x /home/runner sudo chmod o+x /home/runner/work sudo chmod o+x /home/runner/work/cacti sudo chmod o+x /home/runner/work/cacti/cacti - name: Configure Apache Site run: | set -euo pipefail cp ${{ github.workspace }}/tests/tools/cacti.conf /tmp/cacti.conf sed -i "s#GITHUB_WORKSPACE#${{ github.workspace }}#g" /tmp/cacti.conf sudo cp /tmp/cacti.conf /etc/apache2/sites-available/000-default.conf - name: Restart Apache2 run: sudo systemctl restart apache2 - name: Create and Initialize Database run: | set -euo pipefail mysql_config="$RUNNER_TEMP/mysql-client.cnf" install -m 0600 /dev/null "$mysql_config" printf '%s\n' \ '[client]' \ 'user = root' \ 'password = cactiroot' \ 'host = 127.0.0.1' \ 'max_allowed_packet = 500000000' > "$mysql_config" mysql_args=(--defaults-extra-file="$mysql_config") mysql "${mysql_args[@]}" -e "CREATE DATABASE IF NOT EXISTS cacti;" mysql "${mysql_args[@]}" -e "CREATE USER 'cactiuser'@'localhost' IDENTIFIED BY 'cactiuser';" mysql "${mysql_args[@]}" -e "GRANT ALL PRIVILEGES ON cacti.* TO 'cactiuser'@'localhost';" mysql "${mysql_args[@]}" -e "FLUSH PRIVILEGES;" mysql "${mysql_args[@]}" -e "SET GLOBAL max_allowed_packet=500000000;" mysql "${mysql_args[@]}" cacti < "${{ github.workspace }}/cacti.sql" mysql "${mysql_args[@]}" -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/bin/php')" cacti - name: Prime Cacti config.php File run: | set -euo pipefail sudo install -o runner -g www-data -m 0660 \ ${{ github.workspace }}/include/config.php.dist \ ${{ github.workspace }}/include/config.php sudo sed -i "s/localhost/127.0.0.1/g" ${{ github.workspace }}/include/config.php - name: Setup Cacti Daemon run: | set -euo pipefail sudo cp ${{ github.workspace }}/tests/tools/cactid.service /etc/systemd/system/cactid.service sudo sed -i 's/apache/www-data/g' /etc/systemd/system/cactid.service sudo systemctl enable cactid sudo systemctl start cactid for _ in {1..15}; do if systemctl is-active --quiet cactid; then break fi sleep 1 done if ! systemctl is-active --quiet cactid; then sudo systemctl status --no-pager cactid || true sudo journalctl --no-pager -xeu cactid.service || true echo "Cacti daemon failed to become active" exit 1 fi - name: Check install code for validity run: php -q tests/tools/check_install_code.php - name: Check All Resource XML File Syntax run: php tests/tools/check_xml_syntax.php - name: Check Poller Script Syntax after Package Import run: find scripts -name '*.php' -print0 | xargs -0 -n1 php -l - name: Check that all CLI scripts have a valid --version flag run: sudo -u www-data -- bash tests/tools/check_cli_version.sh - name: Install Cacti through the CLI run: sudo -u www-data -- php cli/install_cacti.php --accept-eula --install --force - name: Verify Installer Postconditions run: | set -euo pipefail cd ${{ github.workspace }} mysql_config="$RUNNER_TEMP/mysql-client.cnf" mysql_args=(--defaults-extra-file="$mysql_config") expected_tables=$(grep -c '^CREATE TABLE' cacti.sql) actual_tables=$(mysql "${mysql_args[@]}" cacti --batch --skip-column-names -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()") installed_version=$(mysql "${mysql_args[@]}" cacti --batch --skip-column-names -e "SELECT cacti FROM version") host_templates=$(mysql "${mysql_args[@]}" cacti --batch --skip-column-names -e "SELECT COUNT(*) FROM host_template") test "$actual_tables" -eq "$expected_tables" test "$installed_version" != "new_install" test "$host_templates" -gt 0 - name: Install Cacti Packages through CLI run: sudo -u www-data -- bash tests/tools/install_packages.sh - name: Check All Resource XML File Syntax Post Package Install run: php tests/tools/check_xml_syntax.php - name: Check all perl scripts for syntax errors run: find scripts -name '*.pl' -print0 | xargs -0 -n1 perl -c - name: Audit the Installation Schema run: sudo -u www-data -- php cli/audit_database.php --report - name: Install and Enable Plugins run: sudo -u www-data -- php cli/plugin_manage.php --plugin=thold --plugin=intropage --plugin=monitor --plugin=mactrack --plugin=audit --plugin=flowview --plugin=syslog --install --enable --allperms - name: Add Device for Moc Test, Check CLI scripts for Errors run: | set -euo pipefail sudo -u www-data -- php cli/add_device.php --description=test_device --ip=127.0.0.1 --template=25 sudo -u www-data -- php cli/rebuild_poller_cache.php --debug sudo -u www-data -- php cli/poller_reindex_hosts.php --id=all --debug - name: Run Cacti poller run: | set -euo pipefail sudo -u www-data -- php poller.php --poller=1 --force --debug grep -q "SYSTEM STATS" log/cacti.log # The recursive crawl walks the whole authenticated UI one request at a # time and is the single most expensive step in CI. Nothing it exercises can # change unless server-rendered code, assets or the schema changed, so skip # it when a pull request touches only tests, workflows or documentation. - name: Decide whether the page crawl can affect this change id: crawl_scope run: | set -euo pipefail if [[ "${{ github.event_name }}" != 'pull_request' ]]; then echo 'required=true' >> "$GITHUB_OUTPUT" exit 0 fi base='${{ github.event.pull_request.base.sha }}' # Fail open: if the diff cannot be read, run the crawl. if ! changed=$(git diff --name-only "$base" HEAD 2>/dev/null); then echo 'required=true' >> "$GITHUB_OUTPUT" exit 0 fi if [[ -z "$changed" ]]; then echo 'required=true' >> "$GITHUB_OUTPUT" exit 0 fi if printf '%s\n' "$changed" | grep -qvE '^(tests/|\.github/|docs/|locales/|CHANGELOG|changelog\.d/|.*\.md$)'; then echo 'required=true' >> "$GITHUB_OUTPUT" else echo 'required=false' >> "$GITHUB_OUTPUT" echo "Only tests, workflows or documentation changed; skipping the page crawl." fi - name: Check All Pages Using Recursive wget if: steps.crawl_scope.outputs.required == 'true' run: | set -euo pipefail install -m 0600 /dev/null .my.cnf printf '%s\n' \ '[client]' \ 'user = root' \ 'password = cactiroot' \ 'host = 127.0.0.1' > .my.cnf sudo tests/tools/check_all_pages.sh -vmstat 10 -ps 10 - name: View Cacti log on failure if: failure() run: | set -euo pipefail log_file=${{ github.workspace }}/log/cacti.log if [[ -f "$log_file" ]]; then cat "$log_file" else echo "Cacti log was not created" fi changelog: name: CHANGELOG format runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Check out source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false - name: Validate CHANGELOG ordering and entry format run: python3 .github/scripts/check-changelog.py required: name: CI / required if: always() needs: [runtime-compatibility, native-dependencies, check, changelog] runs-on: ubuntu-slim timeout-minutes: 5 steps: - name: Verify required jobs env: RUNTIME_RESULT: ${{ needs.runtime-compatibility.result }} NATIVE_RESULT: ${{ needs.native-dependencies.result }} INTEGRATION_RESULT: ${{ needs.check.result }} CHANGELOG_RESULT: ${{ needs.changelog.result }} run: | set -euo pipefail for result in \ "$RUNTIME_RESULT" \ "$NATIVE_RESULT" \ "$INTEGRATION_RESULT" \ "$CHANGELOG_RESULT"; do test "$result" = success done