Project: "Where Has Benny Been?" - Command Examples & Scripts
Export all n8n workflows to JSON files for backup/version control
# Using n8n CLI (if installed)
n8n export:workflow --all --output=./workflows/
# Or via API
curl -u "admin:password" \
"http://10.0.0.250:5678/api/v1/workflows" \
| jq '.data[] | {name: .name, id: .id}' > workflows-list.json
# Export individual workflow
curl -u "admin:password" \
"http://10.0.0.250:5678/api/v1/workflows/1" \
> workflow-scan-intake.json
# Via n8n CLI n8n import:workflow --input=./workflow-scan-intake.json # Via API curl -X POST "http://10.0.0.250:5678/api/v1/workflows" \ -u "admin:password" \ -H "Content-Type: application/json" \ -d @workflow-scan-intake.json
# Activate workflow
curl -X PATCH "http://10.0.0.250:5678/api/v1/workflows/1" \
-u "admin:password" \
-H "Content-Type: application/json" \
-d '{"active": true}'
# Deactivate workflow
curl -X PATCH "http://10.0.0.250:5678/api/v1/workflows/1" \
-u "admin:password" \
-H "Content-Type: application/json" \
-d '{"active": false}'
# Connect to PostgreSQL container on bennybeen (10.0.0.250) docker exec -it mm-postgres psql -U mm_user -d madmonkey # Or from host (if port exposed) psql -h 10.0.0.250 -U mm_user -d madmonkey # Connection string format postgresql://mm_user:PASSWORD@10.0.0.250:5432/madmonkey
-- Create database
CREATE DATABASE madmonkey;
-- Connect to database
\c madmonkey
-- Create cards table
CREATE TABLE cards (
qr_id VARCHAR(10) PRIMARY KEY,
type VARCHAR(10) NOT NULL,
status VARCHAR(20) DEFAULT 'active',
current_slug VARCHAR(50),
move_code VARCHAR(20) UNIQUE,
last_scan_ts TIMESTAMP,
last_scan_city VARCHAR(100),
total_scans INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Add indexes
CREATE INDEX idx_cards_type ON cards(type);
CREATE INDEX idx_cards_status ON cards(status);
CREATE INDEX idx_cards_last_scan_ts ON cards(last_scan_ts);
-- Get all verified entries for current month SELECT * FROM entries WHERE verified = true AND month_bucket = TO_CHAR(NOW(), 'YYYY-MM') ORDER BY created_at DESC; -- Get top 10 most scanned cards SELECT qr_id, type, total_scans, last_scan_city FROM cards ORDER BY total_scans DESC LIMIT 10; -- Find stuck cards (no scans in 10+ days) SELECT qr_id, type, last_scan_ts, last_scan_city FROM cards WHERE last_scan_ts < NOW() - INTERVAL '10 days' AND status = 'active' ORDER BY last_scan_ts ASC; -- Get monthly contest entries by city SELECT user_city, COUNT(*) as entries FROM entries WHERE verified = true AND month_bucket = '2024-12' GROUP BY user_city ORDER BY entries DESC; -- Select random winner for the month SELECT entry_id, email, name, qr_id FROM entries WHERE verified = true AND month_bucket = '2024-12' ORDER BY RANDOM() LIMIT 1;
# Backup database docker exec mm-postgres pg_dump -U mm_user madmonkey > backup-$(date +%Y%m%d).sql # Or with compression docker exec mm-postgres pg_dump -U mm_user madmonkey | gzip > backup-$(date +%Y%m%d).sql.gz # Restore database docker exec -i mm-postgres psql -U mm_user madmonkey < backup-20241201.sql # Restore from compressed backup gunzip -c backup-20241201.sql.gz | docker exec -i mm-postgres psql -U mm_user madmonkey
-- Insert test cards
INSERT INTO cards (qr_id, type, status, move_code) VALUES
('N001', 'nest', 'active', NULL),
('N002', 'nest', 'active', NULL),
('T001', 'traveler', 'active', 'MOVE-T001-42XY'),
('T002', 'traveler', 'active', 'MOVE-T002-98ZW');
-- Insert test scan
INSERT INTO scans (qr_id, ip_anon, user_agent, geo_city, geo_region, geo_country)
VALUES ('T001', '203.0.113.0', 'Mozilla/5.0...', 'Chicago', 'Illinois', 'US');
-- Insert test entry
INSERT INTO entries (qr_id, email, name, user_city, verified, month_bucket)
VALUES ('T001', 'test@example.com', 'John Doe', 'Chicago, IL', true, '2024-12');
# Simple GET request (simulates QR scan) curl -L "https://hooks.mad-monkey-creations.com/scan?qr_id=T42" # With custom headers (simulate mobile device) curl -L "https://hooks.mad-monkey-creations.com/scan?qr_id=T42" \ -H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15" \ -H "X-Forwarded-For: 203.0.113.42" # Follow redirects and show headers curl -L -v "https://hooks.mad-monkey-creations.com/scan?qr_id=T42"
# Submit test entry
curl -X POST "https://hooks.mad-monkey-creations.com/entry" \
-H "Content-Type: application/json" \
-d '{
"qr_id": "T42",
"email": "test@example.com",
"name": "John Doe",
"user_city": "Chicago, IL",
"consent": true
}'
# Submit with verbose output
curl -v -X POST "https://hooks.mad-monkey-creations.com/entry" \
-H "Content-Type: application/json" \
-d @test-entry.json
# test-entry.json content:
cat > test-entry.json << EOF
{
"qr_id": "T42",
"email": "test@example.com",
"name": "Test User",
"user_city": "Chicago, IL",
"consent": true
}
EOF
# Simulate Listmonk confirmation webhook
curl -X POST "https://hooks.mad-monkey-creations.com/listmonk/confirm" \
-H "Content-Type: application/json" \
-d '{
"event": "subscriber.confirmed",
"subscriber": {
"id": 123,
"email": "test@example.com",
"name": "John Doe",
"status": "enabled",
"lists": [1]
}
}'
# Create single short URL (Shlink on bennybeen @ 10.0.0.250:8081)
curl -X POST "http://10.0.0.250:8081/rest/v3/short-urls" \
-H "X-Api-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"longUrl": "https://hooks.mad-monkey-creations.com/scan?qr_id=T42",
"customSlug": "T42",
"domain": "mmlnk.us",
"tags": ["benny", "traveler"]
}'
# Bulk create short URLs (using loop)
for i in {1..10}; do
QR_ID=$(printf "T%03d" $i)
curl -X POST "http://10.0.0.250:8081/rest/v3/short-urls" \
-H "X-Api-Key: your-api-key" \
-H "Content-Type: application/json" \
-d "{
\"longUrl\": \"https://hooks.mad-monkey-creations.com/scan?qr_id=$QR_ID\",
\"customSlug\": \"$QR_ID\",
\"domain\": \"mmlnk.us\",
\"tags\": [\"benny\", \"traveler\"]
}"
echo "Created: https://mmlnk.us/$QR_ID"
done
# List all short URLs curl "http://10.0.0.250:8081/rest/v3/short-urls" \ -H "X-Api-Key: your-api-key" # Search by tag curl "http://10.0.0.250:8081/rest/v3/short-urls?tags[]=traveler" \ -H "X-Api-Key: your-api-key" # Paginated results curl "http://10.0.0.250:8081/rest/v3/short-urls?page=1&itemsPerPage=50" \ -H "X-Api-Key: your-api-key" # Search by term curl "http://10.0.0.250:8081/rest/v3/short-urls?searchTerm=T42" \ -H "X-Api-Key: your-api-key"
# Get visits for specific short URL curl "http://10.0.0.250:8081/rest/v3/short-urls/T42/visits" \ -H "X-Api-Key: your-api-key" # Get visits with date range curl "http://10.0.0.250:8081/rest/v3/short-urls/T42/visits?startDate=2024-12-01&endDate=2024-12-31" \ -H "X-Api-Key: your-api-key" # Get aggregated stats curl "http://10.0.0.250:8081/rest/v3/short-urls/T42" \ -H "X-Api-Key: your-api-key" \ | jq '.visitsCount'
# Generate API key using Shlink CLI in container docker exec mm-shlink shlink api-key:generate # Generate with specific permissions docker exec mm-shlink shlink api-key:generate \ --name="n8n-integration" \ --role-name="Domain" # List all API keys docker exec mm-shlink shlink api-key:list
# Check if port is open (telnet) - All services on bennybeen (10.0.0.250) telnet 10.0.0.250 5432 # Check with netcat nc -zv 10.0.0.250 5432 # Check multiple ports on Docker host for port in 5432 5678 8080 8081 9000; do nc -zv -w 2 10.0.0.250 $port done # Use nmap for comprehensive scan nmap -p 5432,5678,8080,8081,9000 10.0.0.250
# Test SMTP submission port 587 (ForwardMail.net relay) openssl s_client -starttls smtp -connect smtp.forwardemail.net:587 # Test ForwardMail.net SMTP telnet smtp.forwardemail.net 587 # Full service port scan on Docker host (external IP is DYNAMIC: 76.150.65.61) nmap -sV -p 80,443 76.150.65.61 # NOTE: Email is handled by ForwardMail.net, not local mail server
# Check if PostgreSQL is accepting connections (all services on 10.0.0.250)
pg_isready -h 10.0.0.250 -p 5432 -U mm_user
# Check HTTP services on bennybeen
curl -I http://10.0.0.250:8081 # Shlink
curl -I http://10.0.0.250:9000 # Listmonk
# Check with timeout
timeout 5 curl -I http://10.0.0.250:5678 || echo "n8n unavailable"
# Check all Mad Monkey services on single Docker host
for service in "10.0.0.250:5432" "10.0.0.250:5678" "10.0.0.250:8080" "10.0.0.250:8081" "10.0.0.250:9000"; do
echo "Checking $service..."
nc -zv -w 2 ${service/:/ } && echo "✓ OK" || echo "✗ FAILED"
done
# Install NVM curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # Load NVM export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # Install Node.js 24.12.0 nvm install 24.12.0 nvm use 24.12.0 nvm alias default 24.12.0 # Verify installation node --version # v24.12.0 npm --version # 9.2.0 # Make available system-wide (create symlink) sudo ln -s "$NVM_DIR/versions/node/$(nvm version)/bin/node" /usr/local/bin/node sudo ln -s "$NVM_DIR/versions/node/$(nvm version)/bin/npm" /usr/local/bin/npm
# Install mini-qr globally
npm install -g mini-qr
# Or install locally in project
mkdir qr-generator && cd qr-generator
npm init -y
npm install mini-qr
# Generate QR code
mini-qr "https://mmlnk.us/T42" -o T42.png
# Generate SVG
mini-qr "https://mmlnk.us/T42" -o T42.svg -t svg
# Generate multiple QR codes
for i in {1..10}; do
QR_ID=$(printf "T%03d" $i)
mini-qr "https://mmlnk.us/$QR_ID" -o "qr-codes/${QR_ID}.png"
echo "Generated: ${QR_ID}.png"
done
// generate-qr-codes.js
const QRCode = require('mini-qr');
const fs = require('fs');
const path = require('path');
// Configuration
const BASE_URL = 'https://mmlnk.us';
const OUTPUT_DIR = './qr-codes';
const CARD_TYPES = {
nest: { prefix: 'N', count: 300 },
traveler: { prefix: 'T', count: 100 }
};
// Create output directory
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
// Generate QR codes
for (const [type, config] of Object.entries(CARD_TYPES)) {
console.log(`Generating ${config.count} ${type} cards...`);
for (let i = 1; i <= config.count; i++) {
const qrId = `${config.prefix}${String(i).padStart(3, '0')}`;
const url = `${BASE_URL}/${qrId}`;
const filename = path.join(OUTPUT_DIR, `${qrId}.png`);
const qr = new QRCode(url);
qr.toFile(filename);
if (i % 50 === 0) {
console.log(` Generated ${i}/${config.count}...`);
}
}
console.log(`✓ Completed ${type} cards\n`);
}
console.log('All QR codes generated successfully!');
node generate-qr-codes.js
# Check A record dig mmlnk.us A +short dig mad-monkey-creations.com A +short # Check MX record dig mad-monkey-creations.com MX +short # Check TXT records (SPF, DKIM, DMARC) dig mad-monkey-creations.com TXT +short dig _dmarc.mad-monkey-creations.com TXT +short dig default._domainkey.mad-monkey-creations.com TXT +short # Use nslookup (alternative) nslookup -type=MX mad-monkey-creations.com nslookup -type=TXT mad-monkey-creations.com # Check DNS propagation dig @8.8.8.8 mmlnk.us # Google DNS dig @1.1.1.1 mmlnk.us # Cloudflare DNS
# Test SMTP with openssl (ForwardMail.net - ONLY email provider) openssl s_client -connect smtp.forwardemail.net:587 -starttls smtp # Send test email via command line cat > test-email.txt << EOF From: hello@mad-monkey-creations.com To: test@example.com Subject: Test Email This is a test email from Mad Monkey Creations. EOF # Send via SMTP (requires mailx or similar) cat test-email.txt | sendmail -v test@example.com
# Install Postfix apt-get update && apt-get install -y postfix # Configure main.cf cat >> /etc/postfix/main.cf << EOF # SMTP Relay Configuration relayhost = [smtp.forwardmail.net]:587 smtp_use_tls = yes smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd smtp_sasl_security_options = noanonymous smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt EOF # Create SASL password file cat > /etc/postfix/sasl_passwd << EOF [smtp.forwardmail.net]:587 username:password EOF # Secure and hash the file chmod 600 /etc/postfix/sasl_passwd postmap /etc/postfix/sasl_passwd # Restart Postfix systemctl restart postfix # Test echo "Test email" | mail -s "Test Subject" test@example.com
# Check SPF record dig mad-monkey-creations.com TXT | grep spf # Check DMARC record dig _dmarc.mad-monkey-creations.com TXT # Check DKIM record dig default._domainkey.mad-monkey-creations.com TXT # Use online tools (open in browser) # https://mxtoolbox.com/SuperTool.aspx # https://www.mail-tester.com/ # https://dkimvalidator.com/ # Command-line DKIM check curl -X POST "https://dkimvalidator.com/api" \ -d "email=test@mad-monkey-creations.com"
# Start all Mad Monkey containers docker-compose -f docker-compose-postgres.yml up -d docker-compose -f docker-compose-shlink.yml up -d docker-compose -f docker-compose-listmonk.yml up -d # Check container status docker ps | grep mm- # View logs docker logs -f mm-postgres docker logs -f mm-shlink --tail 100 docker logs -f mm-listmonk # Restart containers docker restart mm-postgres mm-shlink mm-listmonk # Stop all containers docker stop mm-postgres mm-shlink mm-listmonk # Remove containers (data persists in volumes) docker rm mm-postgres mm-shlink mm-listmonk # View volumes docker volume ls | grep mm # Backup volume docker run --rm -v postgres_data:/data -v $(pwd):/backup \ ubuntu tar czf /backup/postgres-backup-$(date +%Y%m%d).tar.gz /data
# .env file for Mad Monkey project # Copy to .env and fill in actual values # All services run on single Docker host: bennybeen (10.0.0.250) # Server Configuration SERVER_HOST=10.0.0.250 EXTERNAL_IP=76.150.65.61 # DYNAMIC - can change! PROJECT_PATH=/home/bennybeen/mad-monkey/ # PostgreSQL (on 10.0.0.250:5432) POSTGRES_PASSWORD=YOUR_STRONG_PASSWORD_HERE DB_HOST=10.0.0.250 DB_PORT=5432 DB_NAME=madmonkey DB_USER=mm_user # n8n (on 10.0.0.250:5678) N8N_ENCRYPTION_KEY=GENERATE_RANDOM_KEY N8N_BASIC_AUTH_USER=admin N8N_BASIC_AUTH_PASSWORD=YOUR_PASSWORD N8N_URL=http://10.0.0.250:5678 # Listmonk (on 10.0.0.250:9000) LISTMONK_URL=http://10.0.0.250:9000 LISTMONK_API_USER=admin LISTMONK_API_PASSWORD=YOUR_PASSWORD LISTMONK_LIST_ID=1 # Shlink (on 10.0.0.250:8081) SHLINK_URL=http://10.0.0.250:8081 SHLINK_API_KEY=GENERATE_VIA_CLI DEFAULT_DOMAIN=mmlnk.us # Email (ForwardMail.net - ONLY provider) SMTP_HOST=smtp.forwardemail.net SMTP_PORT=587 SMTP_USER=hello@mad-monkey-creations.com SMTP_PASS=YOUR_SMTP_PASSWORD MM_SENDER_EMAIL=hello@mad-monkey-creations.com # Application MM_LANDING_BASE=https://www.mad-monkey-creations.com MM_REWARD_URL=https://www.mad-monkey-creations.com/reward MM_DISCOUNT_CODE=BANANAS10 # External APIs (optional) IPAPI_KEY= ETSY_API_KEY= PRINTIFY_API_TOKEN= # To load in shell: # export $(cat .env | xargs)