Add schema baseline, fix cron/retention, restore cleanup, correct docs
Lint / PHP (phpcs PSR-12) (push) Successful in 26s
Lint / JS (eslint) (push) Successful in 12s
Lint / PHP requirements (version + extensions) (push) Successful in 21s
Security / PHP Security (semgrep) (push) Successful in 1m11s
Lint / Deploy (push) Successful in 2s
Lint / Notify on failure (push) Has been skipped
Lint / PHP (phpcs PSR-12) (pull_request) Successful in 47s
Lint / JS (eslint) (pull_request) Successful in 12s
Lint / PHP requirements (version + extensions) (pull_request) Successful in 59s
Security / PHP Security (semgrep) (pull_request) Successful in 1m6s
Lint / Deploy (pull_request) Has been skipped
Lint / Notify on failure (pull_request) Has been skipped

- migrations/000_baseline.sql: full schema baseline captured from prod
  (validated on a throwaway DB: 17 tables/17 FKs), so the schema is
  reproducible for fresh installs / disaster recovery
- create_recurring_tickets cron: send the Matrix ticket-created
  notification and invalidate the stats cache like the other create paths
- create_ticket_api.php + TicketController::create: invalidate the stats
  cache on create/escalate/reopen so dashboard counts aren't stale
- scripts/cleanup_orphan_uploads.php: restored, made safe (24h mtime
  grace, 9-digit-dir only, skips avatars/symlinks, matches the unique
  filename column, --dry-run)
- cron/cleanup_audit_log.php: enforce the configured audit-log retention
  (deleteOldLogs was implemented but never called)
- README: correct CSRF-rotation, hwmon dedup (no 24h window), SLA (no P3),
  stats-cache callers, and the project structure/endpoint listing
- .env.example: document TRUSTED_PROXIES fail-open risk and .env quoting

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 16:11:26 -04:00
co-authored by Claude Opus 4.8
parent 27a5db8c85
commit d6214a0339
9 changed files with 601 additions and 128 deletions
+38 -16
View File
@@ -73,7 +73,7 @@ The following features are intentionally **not planned** for this system:
- **Duplicate Detection**: Similarity check on ticket title surfaces potential duplicates with one-click linking
- **Activity Timeline**: Full `lt-timeline` audit trail — color-coded by event type (status, comment, assign, attach)
- **Watcher Avatars**: Avatar group shows who is watching a ticket; tooltip lists all names
- **SLA Timer**: P1/P2 tickets display a live elapsed-time banner with progress bar (P1 = 8 h, P2 = 24 h, P3 = 72 h)
- **SLA Timer**: P1/P2 tickets display a live elapsed-time banner with progress bar (P1 = 8 h, P2 = 24 h). Lower priorities (P3P5) have no SLA banner.
- **Priority Alert Banner**: P1 shows a sticky error banner; P2 shows a warning banner — dismissible per session
### Ticket Templates
@@ -121,7 +121,7 @@ The following features are intentionally **not planned** for this system:
- **Powered by audit_log**: No extra table — notifications are derived from existing audit trail
### Matrix Notifications (hookshot)
- **Ticket Created**: Fires when any ticket is created (manual or via API)
- **Ticket Created**: Fires when a ticket is created via the manual form, the external API (hwmonDaemon), or the recurring-ticket cron. (Cloned tickets do not fire this event.)
- **Status Changed**: Fires on every status transition
- **@Mentions**: Mentioned users receive a direct Matrix notification
- **Assignment**: Optional — set `MATRIX_NOTIFY_ASSIGNMENTS=1` to enable
@@ -150,7 +150,7 @@ The following features are intentionally **not planned** for this system:
| `?` | Show keyboard shortcuts help |
### Security Features
- **CSRF Protection**: Token-based protection with constant-time comparison; token rotated after each write
- **CSRF Protection**: Token-based protection with constant-time comparison. `bootstrap.php` rotates the token on a successful write and returns the current token in every response (including on rejection); the client (`lt.api`) resyncs from that value. Rejected requests do not rotate the token.
- **Rate Limiting**: Session-based AND IP-based rate limiting to prevent abuse
- **Security Headers**: CSP with nonces (no unsafe-inline), X-Frame-Options, X-Content-Type-Options
- **SQL Injection Prevention**: All queries use prepared statements with parameter binding
@@ -179,9 +179,9 @@ Content-Type: application/json
**Key behaviours:**
- Authenticated via `Authorization: Bearer` header — API key stored in `/etc/hwmonDaemon/.env`
- **Deduplication**: Generates a SHA-256 hash from the issue category, hostname, and device; rejects duplicate tickets within 24 hours
- **Deduplication**: Generates a SHA-256 hash from the issue category, hostname, and device (no time window). A repeat alert matching an existing **open** ticket updates its title/description and escalates the priority if the condition worsened; if the matching ticket was already **closed**, it is reopened instead of creating a new one
- Cluster-wide issues (Ceph health, etc.) deduplicate across all nodes (hostname excluded from hash)
- Matrix notification sent automatically after ticket creation
- Matrix notification sent automatically on ticket creation, priority escalation, and reopen
- API key must be generated at `/admin/api-keys`; the key goes in hwmonDaemon's `/etc/hwmonDaemon/.env` as `TICKET_API_KEY`
## Technical Architecture
@@ -240,6 +240,11 @@ Content-Type: application/json
- `tickets`: `ticket_id` (unique), `status`, `priority`, `created_at`, `created_by`, `assigned_to`, `visibility`
- `audit_log`: `user_id`, `action_type`, `entity_type`, `created_at`
### Database Schema / Migrations
- `migrations/000_baseline.sql` is the full schema baseline for the whole database. It is written to be safe to re-run (idempotent) and is the source of truth for a fresh install.
- `php migrations/migrate.php` applies any pending migration files in `migrations/` in order, tracking applied files in the `migrations` table. Use `--status` to list state and `--dry-run` to preview without executing.
### API Endpoints
| Endpoint | Method | Description |
@@ -248,6 +253,7 @@ Content-Type: application/json
| `/api/update_ticket.php` | POST | Update ticket with workflow validation |
| `/api/assign_ticket.php` | POST | Assign ticket to user |
| `/api/add_comment.php` | POST | Add comment to ticket |
| `/api/get_comments.php` | GET | Fetch paginated comments for a ticket |
| `/api/clone_ticket.php` | POST | Clone an existing ticket |
| `/api/get_template.php` | GET | Fetch ticket template |
| `/api/get_users.php` | GET | Get user list for assignments |
@@ -292,6 +298,7 @@ tinker_tickets/
│ ├── download_attachment.php # GET: Download with visibility check
│ ├── export_tickets.php # GET: Export tickets to CSV/JSON
│ ├── generate_api_key.php # POST: Generate API key (admin)
│ ├── get_comments.php # GET: Fetch paginated ticket comments
│ ├── get_template.php # GET: Fetch ticket template
│ ├── get_users.php # GET: Get user list
│ ├── health.php # GET: Health check endpoint
@@ -329,14 +336,20 @@ tinker_tickets/
├── config/
│ └── config.php # Config + .env loading
├── controllers/
│ ├── CommentController.php # Comment create/edit/delete + notifications
│ ├── DashboardController.php # Dashboard with stats + filters
│ └── TicketController.php # Ticket CRUD + timeline + visibility
├── cron/
│ ├── cleanup_audit_log.php # Delete audit_log rows past retention (daily)
│ ├── cleanup_ratelimit.php # Purge expired rate-limit files (every few min)
│ └── create_recurring_tickets.php # Process recurring ticket schedules
├── helpers/
│ ├── CacheHelper.php # File-based cache (stats, avatars)
│ ├── Database.php # Centralized mysqli connection
│ ├── ErrorHandler.php # Global error/exception handler
│ ├── NotificationHelper.php # Matrix hookshot webhook events
│ ├── OutputHelper.php # Safe HTML output helpers
│ ├── ResponseHelper.php # JSON API response helpers
│ ├── SynapseHelper.php # Resolves usernames → Matrix IDs via Synapse admin API
│ └── UrlHelper.php # Canonical ticket URLs using APP_DOMAIN
├── middleware/
@@ -347,6 +360,7 @@ tinker_tickets/
│ └── SecurityHeadersMiddleware.php # CSP headers with per-request nonce generation
├── models/
│ ├── ApiKeyModel.php # API key generation/validation
│ ├── AttachmentModel.php # Ticket file attachment metadata
│ ├── AuditLogModel.php # Audit logging + timeline
│ ├── BulkOperationsModel.php # Bulk operations tracking
│ ├── CommentModel.php # Comment data access
@@ -360,11 +374,12 @@ tinker_tickets/
│ ├── UserModel.php # User management + groups
│ ├── UserPreferencesModel.php # User preferences
│ └── WorkflowModel.php # Status transition workflows
├── migrations/
│ ├── 000_baseline.sql # Full schema baseline (safe to re-run)
│ └── migrate.php # CLI migration runner (tracks applied migrations)
├── scripts/
│ ├── add_closed_at_column.php # Migration: add closed_at column to tickets
── add_comment_updated_at.php # Migration: add updated_at column to ticket_comments
│ ├── cleanup_orphan_uploads.php # Clean orphaned uploads (run manually or via cron)
│ └── create_dependencies_table.php # Create ticket_dependencies table
│ ├── check_requirements.php # Verify PHP extensions/config prerequisites
── cleanup_orphan_uploads.php # Delete orphaned upload files past grace period (cron)
├── uploads/ # File attachment storage
│ └── avatars/ # lldap avatar disk cache
├── views/
@@ -455,13 +470,20 @@ AVATAR_CACHE_TTL=3600
### 2. Cron Jobs
Add to crontab for recurring tickets and optional cleanup:
Add to crontab for recurring tickets and maintenance cleanup:
```bash
# Run every hour to create scheduled recurring tickets
0 * * * * php /path/to/tinkertickets/cron/create_recurring_tickets.php
# Optional: clean up orphaned uploads weekly
0 3 * * 0 php /path/to/tinkertickets/scripts/cleanup_orphan_uploads.php
# Purge expired rate-limit files (every 5 minutes)
*/5 * * * * php /path/to/tinkertickets/cron/cleanup_ratelimit.php
# Delete audit_log rows older than AUDIT_LOG_RETENTION_DAYS (daily)
30 3 * * * php /path/to/tinkertickets/cron/cleanup_audit_log.php
# Delete orphaned upload files with no attachment row, past a 24h grace period (daily).
# Add --dry-run to preview without deleting.
0 4 * * * php /path/to/tinkertickets/scripts/cleanup_orphan_uploads.php
```
### 3. File Uploads
@@ -502,7 +524,7 @@ Key conventions and gotchas for working with this codebase:
3. **Admin check**: `$_SESSION['user']['is_admin'] ?? false`
4. **Config path**: `config/config.php` (not `config/db.php`)
5. **Comments table**: `ticket_comments` (not `comments`)
6. **CSRF**: Required for all POST/DELETE requests via `X-CSRF-Token` header; bootstrap.php rotates token and returns it in `csrf_token` field of all `apiRespond()` responses
6. **CSRF**: Required for all POST/DELETE requests via `X-CSRF-Token` header. `bootstrap.php` rotates the token only on a successful write and returns the current token in the `csrf_token` field of every `apiRespond()` response (including rejections), so the client can resync. A rejected request keeps the existing token.
7. **Cache busting**: `ASSET_VERSION` is auto-computed from asset file mtimes; override with `ASSET_VERSION=` in `.env`
8. **Ticket linking**: Use `#123456789` in markdown-enabled comments
9. **User groups**: Stored in `users.groups` as comma-separated values
@@ -520,8 +542,8 @@ Key conventions and gotchas for working with this codebase:
21. **Confirm dialogs**: Never use browser `confirm()`. Use `showConfirmModal(title, message, type, onConfirm)` (defined in `utils.js`, available on all pages). Types: `'warning'` | `'error'` | `'info'`.
22. **`utils.js` on all pages**: `utils.js` is loaded by all views (including admin). It provides `escapeHtml()`, `getTicketIdFromUrl()`, and `showConfirmModal()`.
23. **No `toast.js`**: `toast.js` is deprecated and no longer loaded by any view. Use `lt.toast.success/error/warning/info()` directly from `base.js`.
24. **Stats cache**: `StatsModel` caches stats for 60 s. Any API that modifies ticket state must call `(new StatsModel($conn))->invalidateCache()` after changes (bulk_operation, assign_ticket, update_ticket, clone_ticket all do this).
25. **External API (`create_ticket_api.php`)**: Uses `ApiKeyAuth` (Bearer token), not session auth. Served directly by the web server from the document root — not through the index.php router. Includes deduplication logic to prevent duplicate hw-alert tickets within 24 h.
24. **Stats cache**: `StatsModel` caches stats for 60 s. Any path that modifies ticket state must call `(new StatsModel($conn))->invalidateCache()` after the change. Callers: `TicketController::create` (manual create), `create_ticket_api.php` (external API create/escalate/reopen), `cron/create_recurring_tickets.php`, `bulk_operation`, `assign_ticket`, `update_ticket`, and `clone_ticket`.
25. **External API (`create_ticket_api.php`)**: Uses `ApiKeyAuth` (Bearer token), not session auth. Served directly by the web server from the document root — not through the index.php router. Includes deduplication logic (SHA-256 hash, no time window) that updates/escalates an existing open duplicate or reopens a closed one rather than creating a new ticket.
## File Reference
@@ -557,7 +579,7 @@ Key conventions and gotchas for working with this codebase:
|---------|---------------|
| SQL Injection | All queries use prepared statements with parameter binding |
| XSS Prevention | HTML escaped in markdown parser; CSP with per-request nonces |
| CSRF Protection | Token-based with constant-time comparison (`hash_equals`); rotated on each write |
| CSRF Protection | Token-based with constant-time comparison (`hash_equals`); rotated on successful writes, current token returned in every response (including rejections) for the client to resync — rejected requests do not rotate |
| Session Security | Fixation prevention, secure cookies, session timeout |
| Rate Limiting | Session-based + IP-based (file storage) |
| File Security | Path traversal prevention, MIME type validation, uploads `.htaccess` blocks execution |