From 45019bc38f9322705c6d44685c53781816eaa6b4 Mon Sep 17 00:00:00 2001 From: Jasper Frumau Date: Fri, 21 Aug 2026 10:37:29 +0700 Subject: [PATCH 1/4] docs: forbid AI attribution in commit messages The convention said Co-Authored-By lines for Claude or Mistral were permitted. They are not wanted: commit messages should carry the change and its reasoning and nothing else, which is the rule PR descriptions already followed. --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 493c656..ea26c00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -302,5 +302,5 @@ Before running backup operations on production: ### Git Commit and PR Conventions - **Atomic commits**: Each commit should represent a single logical change. Do not bundle unrelated changes in one commit. This makes history easier to review and revert when needed. -- **AI co-authorship in commits is allowed**: `Co-Authored-By` lines for Claude or Mistral are permitted in commit messages in this repo. +- **No AI attribution in commits**: do not add `Co-Authored-By` lines for Claude or Mistral, a `Claude-Session` trailer, or any other tool reference. Commit messages carry the change and its reasoning, nothing else. This matches the PR-description rule below. - **PR descriptions**: Pull request descriptions should be professional and focused on the changes. Do not include AI attribution footers or tool references. From 22a891c8e7d96a415d57dff1a3f68b1b1236b5f2 Mon Sep 17 00:00:00 2001 From: Jasper Frumau Date: Fri, 21 Aug 2026 10:38:38 +0700 Subject: [PATCH 2/4] fix(monitoring): repair the security mail subject and weekly cron job Two bugs in setup-monitoring.yml that both failed silently, found while porting these playbooks into the trellis-wp-monitoring Ansible role. The security alert built its subject with an unquoted $(date +%Y-%m-%d %H:%M). date takes %H:%M as a second operand and errors out, so mail received a truncated subject and '%H:%M' as an extra recipient. Quoting the format string as one argument fixes it. The weekly summary was an inline cron command containing $(date +%Y-%m-%d). cron treats an unescaped % as end-of-command and pipes the remainder to the job's stdin, so that entry was truncated mid-word and never wrote a file. It now calls a wrapper script like the other two reports already did, which keeps % out of the crontab entirely, and gains the 90-day cleanup the other two had. Verified by rendering every wrapper with and without an alert email (bash -n clean) and asserting no crontab entry contains an unescaped %. --- trellis/monitoring/setup-monitoring.yml | 27 +++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/trellis/monitoring/setup-monitoring.yml b/trellis/monitoring/setup-monitoring.yml index 7582977..b66e6f6 100644 --- a/trellis/monitoring/setup-monitoring.yml +++ b/trellis/monitoring/setup-monitoring.yml @@ -111,7 +111,10 @@ # Send email only if alerts found if grep -q "\[ALERT\]" "$REPORT_FILE"; then if command -v mail &> /dev/null; then - mail -s "[SECURITY ALERT] {{ site }} - $(date +%Y-%m-%d %H:%M)" {{ alert_email }} < "$REPORT_FILE" + # The format string must be one quoted argument: unquoted, + # date takes %H:%M as a second operand, errors out, and mail + # gets a truncated subject plus a bogus recipient. + mail -s "[SECURITY ALERT] {{ site }} - $(date '+%Y-%m-%d %H:%M')" {{ alert_email }} < "$REPORT_FILE" fi fi {% endif %} @@ -141,13 +144,33 @@ user: "{{ web_user }}" state: present + - name: Create wrapper script for weekly traffic summary + copy: + content: | + #!/bin/bash + # Weekly traffic summary for {{ site }} + # + # A wrapper rather than an inline cron command on purpose: cron + # treats an unescaped `%` as end-of-command and pipes the rest to + # the job's stdin, so a crontab line containing $(date +%Y-%m-%d) + # is silently truncated mid-word and produces nothing. + REPORT_FILE="{{ monitoring_dir }}/logs/weekly-summary-$(date '+%Y-%m-%d').txt" + {{ monitoring_dir }}/traffic-monitor.sh {{ log_file }} 168 > "$REPORT_FILE" 2>&1 + + # Clean up old summaries (keep last 90 days) + find {{ monitoring_dir }}/logs -name "weekly-summary-*.txt" -mtime +90 -delete + dest: "{{ monitoring_dir }}/weekly-summary.sh" + owner: "{{ web_user }}" + group: "{{ web_user }}" + mode: '0755' + - name: Setup cron job for weekly summary cron: name: "Weekly traffic summary for {{ site }}" minute: "0" hour: "9" weekday: "1" - job: "{{ monitoring_dir }}/traffic-monitor.sh {{ log_file }} 168 > {{ monitoring_dir }}/logs/weekly-summary-$(date +%Y-%m-%d).txt 2>&1" + job: "{{ monitoring_dir }}/weekly-summary.sh" user: "{{ web_user }}" state: present From 553cc6fa314d38fb947055baad36dff24834e784 Mon Sep 17 00:00:00 2001 From: Jasper Frumau Date: Fri, 21 Aug 2026 10:40:37 +0700 Subject: [PATCH 3/4] docs: document the trellis ops surface in the README The Trellis section pointed at documentation but never said what commands exist, and nothing outside the plugin section mentioned that trellis-cli users reach all of this as 'trellis ops <...>'. It now opens with that equivalence and a generated table of all 27 @platform trellis commands, grouped as trellis ops presents them, with the server-executed log readers marked. The old documentation table is kept under a Guides heading. The plugin section gains the detail that matters most in practice: plugins register from PATH before trellis-cli resolves a project, so trellis ops runs anywhere, and the Ansible commands find the project through wp-ops' own detection. --- README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 64f1980..d8d871b 100644 --- a/README.md +++ b/README.md @@ -87,9 +87,14 @@ trellis ops search backup `trellis ops` scopes its **listing** to the commands tagged `@platform trellis`; run plain `wp-ops` for the full catalog. Running a command is never scoped — name -any command and it works. Unlike core `trellis` subcommands, a plugin doesn't need -you to be inside a Trellis project; the playbook commands find the project the same -way `wp-ops` always has. +any command and it works. See the [Trellis command reference](#command-reference) +for what that surface contains. + +Unlike core `trellis` subcommands, a plugin doesn't need you to be inside a Trellis +project — trellis-cli registers plugins from `$PATH` before it resolves a project at +all, so `trellis ops doctor` runs from anywhere while `trellis info` refuses. The +Ansible commands do still need a project, but they always did: wp-ops locates it +itself by walking up from your current directory, exactly as under bare `wp-ops`. Requires trellis-cli new enough to have plugin support (v1.19.0 or later) and the default `load_plugins: true`. If you built from source instead of installing the @@ -110,6 +115,7 @@ no prompt to confirm a detected guess: # Ansible playbooks (wp-ops trellis ) need a Trellis project's ansible.cfg/inventory/group_vars export TRELLIS_DIR=/path/to/your/trellis wp-ops trellis database-backup -e site=example.com -e env=production +trellis ops trellis database-backup -e site=example.com -e env=production # identical # WP-CLI scripts (wp-ops wp-cli