#!/usr/bin/env node /** * =============================================================================== * MATTERMOST SSH-BASED BTCPAY WEBHOOK * =============================================================================== * Created: September 10, 2025 * Purpose: SSH-based webhook to retrieve BTCPay onion addresses via Mattermost * Domain: thebankofdebbie.giize.com * Method: SSH connection to retrieve data (no persistent web server) */ const express = require('express'); const { exec } = require('child_process'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 3002; // Configuration const config = { domain: 'thebankofdebbie.giize.com', ssh_host: 'thebankofdebbie.giize.com', ssh_port: 2255, ssh_user: 'sysadmin', ssh_key_path: '/home/sysadmin/.ssh/vps_hardening_key', // Adjust path as needed mattermost_token: 'dr7gz6xwmt8qjg71wxcqjwqz1r', allowed_users: ['admin', 'sysadmin', 'bankofdebbie'] }; app.use(express.json()); app.use(express.urlencoded({ extended: true })); /** * Execute SSH command to retrieve onion addresses */ function getOnionAddresses() { return new Promise((resolve, reject) => { const sshCmd = `ssh -i ${config.ssh_key_path} -p ${config.ssh_port} -o StrictHostKeyChecking=no ${config.ssh_user}@${config.ssh_host} " echo 'BTCPay_Onion:' && sudo cat /var/lib/docker/volumes/generated_tor_servicesdir/_data/BTCPayServer/hostname 2>/dev/null || echo 'pending'; echo 'Bitcoin_Onion:' && sudo cat /var/lib/docker/volumes/generated_tor_servicesdir/_data/BTC-P2P/hostname 2>/dev/null || echo 'pending'; echo 'Disk_Usage:' && df -h / | grep -v Filesystem | awk '{print \$3 \" used / \" \$2 \" total\"}'; echo 'Bitcoin_Status:' && docker exec btcpayserver_bitcoind bitcoin-cli getblockchaininfo 2>/dev/null | jq -r '{blocks, headers, pruned}' || echo 'syncing' "`; exec(sshCmd, { timeout: 30000 }, (error, stdout, stderr) => { if (error) { reject(new Error(`SSH command failed: ${error.message}`)); return; } try { const lines = stdout.split('\n').filter(line => line.trim()); const result = { btcpay_onion: 'pending', bitcoin_onion: 'pending', disk_usage: 'unknown', bitcoin_status: 'syncing' }; lines.forEach(line => { if (line.startsWith('BTCPay_Onion:')) { result.btcpay_onion = line.split('BTCPay_Onion:')[1].trim(); } else if (line.startsWith('Bitcoin_Onion:')) { result.bitcoin_onion = line.split('Bitcoin_Onion:')[1].trim(); } else if (line.startsWith('Disk_Usage:')) { result.disk_usage = line.split('Disk_Usage:')[1].trim(); } else if (line.startsWith('Bitcoin_Status:')) { result.bitcoin_status = line.split('Bitcoin_Status:')[1].trim(); } }); resolve(result); } catch (parseError) { reject(new Error(`Failed to parse SSH output: ${parseError.message}`)); } }); }); } /** * Get BTCPay system status via SSH */ function getSystemStatus() { return new Promise((resolve, reject) => { const sshCmd = `ssh -i ${config.ssh_key_path} -p ${config.ssh_port} -o StrictHostKeyChecking=no ${config.ssh_user}@${config.ssh_host} " echo 'Container_Count:' && docker ps | grep -E '(btcpay|bitcoin|tor)' | wc -l; echo 'Uptime:' && uptime | awk '{print \$3 \$4}' | sed 's/,//'; echo 'Bitcoin_Pruned:' && docker logs btcpayserver_bitcoind 2>&1 | grep -i 'prune configured' | tail -1 | grep -o '[0-9]* MiB' || echo 'checking' "`; exec(sshCmd, { timeout: 20000 }, (error, stdout) => { if (error) { resolve('Status check failed'); return; } const lines = stdout.split('\n').filter(line => line.trim()); const result = {}; lines.forEach(line => { if (line.startsWith('Container_Count:')) { result.containers = line.split('Container_Count:')[1].trim() + ' containers'; } else if (line.startsWith('Uptime:')) { result.uptime = line.split('Uptime:')[1].trim(); } else if (line.startsWith('Bitcoin_Pruned:')) { result.pruning = line.split('Bitcoin_Pruned:')[1].trim(); } }); resolve(result); }); }); } /** * Main webhook endpoint for Mattermost */ app.post('/webhook/btcpay', async (req, res) => { try { const { token, user_name, text, trigger_word } = req.body; // Validate token if (token !== config.mattermost_token) { return res.status(401).json({ text: 'Unauthorized: Invalid token' }); } // Check if user is authorized if (!config.allowed_users.includes(user_name)) { return res.status(403).json({ text: `❌ Access denied for user: ${user_name}. Contact admin for BTCPay access.` }); } // Parse command const command = text.toLowerCase().trim(); const isOnionCommand = command.includes('onion') || command.includes('btcpay') || command.includes('tor'); const isStatusCommand = command.includes('status'); const isHelpCommand = command.includes('help'); if (isHelpCommand) { return res.json({ text: `## BTCPay Server Commands (SSH-based)\n\n` + `**Available commands:**\n` + `• \`!btcpay onion\` - Get onion addresses\n` + `• \`!btcpay status\` - Get system status\n` + `• \`!btcpay help\` - Show this help\n\n` + `**Domain:** ${config.domain}\n` + `**Method:** SSH-based retrieval\n` + `**User:** ${user_name} ✅` }); } if (isOnionCommand || isStatusCommand) { // Retrieve data via SSH const [onionData, statusData] = await Promise.all([ getOnionAddresses().catch(err => ({ error: err.message })), isStatusCommand ? getSystemStatus().catch(err => ({ error: err.message })) : Promise.resolve({}) ]); if (onionData.error) { return res.json({ text: `❌ **Error retrieving BTCPay data:**\n\`\`\`\n${onionData.error}\n\`\`\`\n\nPlease check VPS connectivity.` }); } let statusInfo = ''; if (isStatusCommand && !statusData.error) { statusInfo = `\n\n**📊 System Status:**\n` + `**Containers:** ${statusData.containers || 'checking...'}\n` + `**Uptime:** ${statusData.uptime || 'checking...'}\n` + `**Bitcoin:** ${statusData.pruning || 'Pruned mode active'}\n` + `**Disk:** ${onionData.disk_usage}\n` + `**Sync:** ${onionData.bitcoin_status}`; } // Format response const response = { text: `## 🧅 BTCPay Server Information (SSH Retrieved)\n\n` + `**🌐 Domain:** https://${config.domain}\n\n` + `**🧅 Tor Hidden Services:**\n` + `• **BTCPay:** \`${onionData.btcpay_onion}\`\n` + `• **Bitcoin P2P:** \`${onionData.bitcoin_onion}\`\n\n` + `**🔐 Access Methods:**\n` + `• **Clearnet:** https://${config.domain}\n` + `• **Tor Browser:** http://${onionData.btcpay_onion}\n` + `• **SSH Access:** \`ssh -p ${config.ssh_port} ${config.ssh_user}@${config.domain}\`\n\n` + `**⚡ API Integration:**\n` + `• **REST API:** https://${config.domain}/api\n` + `• **Tor API:** http://${onionData.btcpay_onion}/api\n\n` + `**🔒 Security:** Hardened Debian 13, Tor-only Bitcoin, SSH-based monitoring\n` + `**📅 Retrieved:** ${new Date().toLocaleString()}\n` + `**👤 Requested by:** ${user_name}` + statusInfo }; return res.json(response); } // Default response return res.json({ text: `❓ Unknown command. Use \`!btcpay help\` for available commands.\n\n` + `**Quick access:**\n` + `• \`!btcpay onion\` - Get Tor onion addresses\n` + `• \`!btcpay status\` - Get full system status` }); } catch (error) { console.error('Webhook error:', error); return res.status(500).json({ text: `❌ Error retrieving BTCPay information: ${error.message}` }); } }); /** * Health check endpoint */ app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'BTCPay SSH Webhook', domain: config.domain, method: 'SSH-based retrieval', timestamp: new Date().toISOString() }); }); /** * Info endpoint - SSH-based onion address retrieval */ app.get('/info', async (req, res) => { try { const data = await getOnionAddresses(); res.json({ domain: config.domain, btcpay_onion: data.btcpay_onion, bitcoin_onion: data.bitcoin_onion, clearnet_url: `https://${config.domain}`, api_url: `https://${config.domain}/api`, tor_api_url: `http://${data.btcpay_onion}/api`, disk_usage: data.disk_usage, bitcoin_status: data.bitcoin_status, method: 'SSH retrieval', timestamp: new Date().toISOString() }); } catch (error) { res.status(500).json({ error: error.message, method: 'SSH retrieval failed' }); } }); /** * Start server */ app.listen(PORT, '127.0.0.1', () => { console.log(`🚀 BTCPay SSH Webhook Server running on localhost:${PORT}`); console.log(`📡 Domain: ${config.domain}`); console.log(`🔑 Method: SSH-based onion address retrieval`); console.log(`💡 Endpoints:`); console.log(` POST /webhook/btcpay - Main webhook (SSH-based)`); console.log(` GET /info - Info endpoint (SSH-based)`); console.log(` GET /health - Health check`); console.log(`\n🔧 Mattermost Setup:`); console.log(` Trigger: !btcpay`); console.log(` URL: Use SSH tunnel to access localhost:${PORT}/webhook/btcpay`); console.log(` Token: ${config.mattermost_token}`); console.log(`\n🔒 Security: Binds to localhost only, uses SSH keys for data retrieval`); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('🛑 Shutting down SSH webhook server...'); process.exit(0); }); module.exports = app;