wiki-vault/Sources/Dev/instagram-automation-session-notes.md
Travis Herbranson 34a268d8dc migration: copy 62 notes from pbs-projects and homelab-projects
Two-layer structure: Sources (raw notes) + Wiki (compile output)
Four domains: Dev (40), Venture (3), Homelab (23), Reference (0)
Includes CLAUDE.md spec, index pages at all levels, compile log

Co-Authored-By: Lovebug <lovebug@herbylab.dev>
2026-05-08 17:21:13 -04:00

5.7 KiB

created path project status tags type updated
2026-03-16 Sources/Dev instagram-automation-session-notes active
pbs
n8n
instagram
mysql
wordpress
automation
docker
session-notes 2026-03-16

Session Notes — MySQL Schema, Error Checking & The Great Lockout of 2026

Accomplished This Session

Error Checking — Instagram Reply Workflow

All four error checks are now wired to Notify Travis → Google Chat:

  • Subscribe check notification
  • Hash verification failure notification (with alert level)
  • Empty table lookup notification (includes Reel ID, commenter, comment text)
  • Keyword not matched notification

Key learnings:

  • n8n "Always Output Data" needed on MySQL node so IF node can evaluate empty results
  • When "Always Output Data" is on, check $('node').first().json.id exists instead of length > 0 (length returns 1 for empty item)
  • Google Chat HTTP Request node must use "Using Fields Below" mode, not raw JSON — literal newlines from expressions break JSON
  • Build message strings as single ={{ }} expression using + concatenation to keep \n as escaped characters
  • $now.toFormat('MMM dd, HH:mm:ss') works in both test and live contexts (unlike $execution.startedAt)
  • Timestamp and title passed from calling workflow, Notify Travis just passes them through

MySQL Schema — New Tables Created

pbs_recipes:

CREATE TABLE IF NOT EXISTS pbs_recipes (
    id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    post_id      BIGINT UNSIGNED NOT NULL,
    title        VARCHAR(500)    NOT NULL,
    url          VARCHAR(500)    NOT NULL,
    keyword      VARCHAR(100)    NOT NULL,
    created_at   DATETIME        DEFAULT CURRENT_TIMESTAMP,
    updated_at   DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY (post_id)
);

instagram_posts:

CREATE TABLE IF NOT EXISTS instagram_posts (
    id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    reel_id      VARCHAR(50)     NOT NULL,
    post_id      BIGINT UNSIGNED NULL,
    created_at   DATETIME        DEFAULT CURRENT_TIMESTAMP,
    updated_at   DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY (reel_id),
    FOREIGN KEY (post_id) REFERENCES pbs_recipes(post_id)
);

Key decisions:

  • post_id is nullable in instagram_posts — NULL means unmatched reel, not an error
  • reel_id stored as VARCHAR not BIGINT (Instagram ID integer overflow issue)
  • Normalized schema — keyword and URL live in pbs_recipes only, JOIN at query time
  • Staying on shared MySQL instance — at current scale (2k/month, 10-20 replies) separate instance is overkill
  • phpMyAdmin added back to Docker Compose for direct MySQL access

WordPress Publish Webhook — Proof of Concept Complete

  • WPCode Lite snippet fires on post publish (admin only)
  • Payload: post_id, title, url, tags, categories
  • HMAC signature verification via X-PBS-Signature header
  • Raw body passed via X-PBS-Body header (base64 encoded) for exact signature verification
  • n8n Code node verifies signature before processing
  • Ready to wire into pbs_recipes table insert workflow

Architecture Decision — Reply Workflow Refactor

Current two-table lookup + merge is being replaced with:

  • Single instagram_posts lookup only
  • URL stored directly in instagram_posts (populated at reel insert time from pbs_recipes)
  • Simpler, faster, fewer failure points

🔧 The Great WordPress Lockout of 2026

What happened: Wordfence locked out staging WP admin. Unlock email never arrived.

What we tried (none of it worked):

  • WP-CLI wf:unblock-ip command (not a registered command)
  • Clearing wp_options Wordfence entries in MySQL
  • Disabling Wordfence plugin
  • Restarting WordPress container
  • Deleting wflogs directory

Root cause discovered: Wordfence WAF runs as a PHP auto_prepend_file — completely independent of the plugin being active. Found in .htaccess and wordfence-waf.php in WordPress root.

Actual fix: Wrong email address on the admin account. Changed it, logged right in. 🤦

Real lesson learned: Read the error message carefully before spending an hour troubleshooting! 😄

Lockout duration if we hadn't figured it out: 1 MONTH


📋 Runbook Addition — WordPress Lockout Recovery

# Get into WordPress container
docker exec -it wordpress bash
cd /var/www/html

# List all users
wp --allow-root user list

# Reset password by username
wp --allow-root user update admin --user_pass=newpassword

# Reset password by email
wp --allow-root user update user@email.com --user_pass=newpassword

# If WAF is blocking login page independently of plugin:
# 1. Remove Wordfence WAF block from .htaccess
# 2. Rename wordfence-waf.php to wordfence-waf.php.disabled
# 3. docker restart wordpress

🔜 Next Steps

  • Wire WordPress publish webhook → n8n workflow → insert into pbs_recipes
  • Build one-time migration workflow — n8n datatables → MySQL
  • Finish reply workflow refactor — remove Merge node, single instagram_posts lookup
  • Fix production WordPress admin email (same issue exists there!)
  • Deploy all changes to production once staging is verified

📝 Notes

  • All development on staging (staging.plantbasedsoutherner.com)
  • n8n datatables to be retired once MySQL migration complete
  • Foreign key constraint requires pbs_recipes record to exist before instagram_posts insert
  • Use WHERE post_id IS NULL query to find unmatched reels for Content Hub dashboard

Last Updated: March 16, 2026 Maintained by: Travis Project: Plant Based Southerner — Instagram Automation