#!/usr/bin/env node /** * =============================================================================== * MATTERMOST BTCPAY ONION ADDRESS WEBHOOK * =============================================================================== * Created: September 10, 2025 * Purpose: Retrieve BTCPay Server and Bitcoin onion addresses via Mattermost * Domain: thebankofdebbie.giiz.com * Usage: Post "!btcpay" or "!onion" in Mattermost to get current addresses */ const express = require('express'); const { exec } = require('child_process'); const fs = require('fs'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 3001; // Configuration const config = { domain: 'thebankofdebbie.giize.com', mattermost_token: process.env.MATTERMOST_TOKEN || 'dr7gz6xwmt8qjg71wxcqjwqz1r', btcpay_tor_path: '/var/lib/docker/volumes/generated_tor_servicesdir/_data/BTCPayServer/hostname', bitcoin_tor_path: '/var/lib/docker/volumes/generated_tor_servicesdir/_data/BTC-P2P/hostname', allowed_users: ['admin', 'sysadmin', 'bankofdebbie'], // Add authorized users webhook_secret: process.env.WEBHOOK_SECRET || 'your-secret-here' }; app.use(express.json()); app.use(express.urlencoded({ extended: true })); /** * Utility function to read onion address from file */ function readOnionAddress(filePath) { return new Promise((resolve, reject) => { fs.readFile(filePath, 'utf8', (err, data) => { if (err) { resolve(null); } else { resolve(data.trim()); } }); }); } /** * Get BTCPay Server status */ function getBTCPayStatus() { return new Promise((resolve) => { exec('docker ps --format "table {{.Names}}\\t{{.Status}}" | grep -E "(btcpay|bitcoin|tor)"', (error, stdout) => { if (error) { resolve('BTCPay services status unavailable'); } else { resolve(stdout.trim() || 'No BTCPay services found'); } }); }); } /** * Get Bitcoin sync status */ function getBitcoinSync() { return new Promise((resolve) => { exec('docker exec btcpayserver_bitcoind bitcoin-cli getblockchaininfo 2>/dev/null', (error, stdout) => { if (error) { resolve('Bitcoin RPC not available'); } else { try { const info = JSON.parse(stdout); const progress = (info.verificationprogress * 100).toFixed(2); resolve(`Blocks: ${info.blocks}/${info.headers} (${progress}% synced)${info.pruned ? ' - PRUNED' : ''}`); } catch (e) { resolve('Bitcoin sync data unavailable'); } } }); }); } /** * Get disk usage */ function getDiskUsage() { return new Promise((resolve) => { exec('df -h / | grep -v Filesystem', (error, stdout) => { if (error) { resolve('Disk usage unavailable'); } else { const parts = stdout.trim().split(/\s+/); resolve(`${parts[2]} used / ${parts[1]} total (${parts[4]} full)`); } }); }); } /** * Main webhook endpoint */ app.post('/webhook/btcpay', async (req, res) => { try { // Log the incoming request for debugging console.log('Webhook received:', JSON.stringify(req.body, null, 2)); const { token, team_domain, user_name, text, trigger_word } = req.body; // Validate token (basic security) 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\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` + `**User:** ${user_name}\n` + `**Access:** ✅ Authorized` }); } if (isOnionCommand || isStatusCommand) { // Get onion addresses const [btcpayOnion, bitcoinOnion] = await Promise.all([ readOnionAddress(config.btcpay_tor_path), readOnionAddress(config.bitcoin_tor_path) ]); // Get system status if requested let statusInfo = ''; if (isStatusCommand) { const [btcpayStatus, bitcoinSync, diskUsage] = await Promise.all([ getBTCPayStatus(), getBitcoinSync(), getDiskUsage() ]); statusInfo = `\n\n**📊 System Status:**\n` + `**Bitcoin:** ${bitcoinSync}\n` + `**Disk:** ${diskUsage}\n` + `**Services:** Running\n\n` + `\`\`\`\n${btcpayStatus}\n\`\`\``; } // Format response const response = { text: `## 🧅 BTCPay Server Information\n\n` + `**Domain:** ${config.domain}\n\n` + `**🌐 Clearnet Access:**\n` + `• https://${config.domain}\n\n` + `**🧅 Tor Hidden Services:**\n` + `• **BTCPay:** ${btcpayOnion || '⏳ Generating...'}\n` + `• **Bitcoin P2P:** ${bitcoinOnion || '⏳ Generating...'}\n\n` + `**🔐 Access Methods:**\n` + `• **Tor Browser:** \`http://${btcpayOnion || 'pending'}\`\n` + `• **SSH Tunnel:** \`ssh -L 8080:localhost:80 ubuntu@${config.domain}\`\n\n` + `**⚡ Integration:**\n` + `• **API Endpoint:** \`https://${config.domain}/api\`\n` + `• **Webhook URL:** \`https://${config.domain}/webhook\`\n` + `• **Onion API:** \`http://${btcpayOnion || 'pending'}/api\`\n\n` + `**🔒 Security Status:** ✅ Tor-enabled, Pruned Bitcoin, Hardened VPS\n` + `**📅 Updated:** ${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 commands:**\n` + `• \`!btcpay onion\` - Get onion addresses\n` + `• \`!btcpay status\` - Get 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 Mattermost Webhook', domain: config.domain, timestamp: new Date().toISOString() }); }); /** * Root health endpoint with HTML response */ app.get('/', async (req, res) => { try { const [btcpayOnion, bitcoinOnion, diskUsage] = await Promise.all([ readOnionAddress(config.btcpay_tor_path), readOnionAddress(config.bitcoin_tor_path), getDiskUsage() ]); const html = ` BTCPay Server Health - ${config.domain}

🔒 BTCPay Server Health Status

Domain: ${config.domain}

Status: ✅ OPERATIONAL

Last Updated: ${new Date().toLocaleString()}

🌐 Access Points

Clearnet: https://${config.domain}

Health Dashboard: https://health.${config.domain}

🧅 Tor Hidden Services

BTCPay Server:

${btcpayOnion || '⏳ Generating...'}

Bitcoin P2P Node:

${bitcoinOnion || '⏳ Generating...'}

📊 System Information

Disk Usage: ${diskUsage}

Bitcoin Mode: Pruned (10GB maximum)

Network: Tor-only Bitcoin connections

Security: Hardened Debian 13

⚡ API Integration

REST API: https://${config.domain}/api

Tor API: http://${btcpayOnion || 'pending'}/api

Webhooks: https://${config.domain}/webhook

🤖 Mattermost Integration

Bot Account: bankofdebbie

Commands: !btcpay, !btcpay onion, !btcpay status

Webhook URL: https://health.${config.domain}/webhook

Info API: https://health.${config.domain}/info

`; res.send(html); } catch (error) { res.status(500).send(`

Error

${error.message}

`); } }); /** * Info endpoint for API information (GET request) */ app.get('/info', async (req, res) => { try { const [btcpayOnion, bitcoinOnion, btcpayStatus, diskUsage] = await Promise.all([ readOnionAddress(config.btcpay_tor_path), readOnionAddress(config.bitcoin_tor_path), getBTCPayStatus(), getDiskUsage() ]); res.json({ domain: config.domain, btcpay_onion: btcpayOnion, bitcoin_onion: bitcoinOnion, clearnet_url: `https://${config.domain}`, api_url: `https://${config.domain}/api`, disk_usage: diskUsage, services_status: btcpayStatus, timestamp: new Date().toISOString() }); } catch (error) { res.status(500).json({ error: error.message }); } }); /** * Start server */ app.listen(PORT, () => { console.log(`🚀 BTCPay Mattermost Webhook Server running on port ${PORT}`); console.log(`📡 Domain: ${config.domain}`); console.log(`🧅 Monitoring onion services...`); console.log(`💡 Endpoints:`); console.log(` POST /webhook/btcpay - Main webhook`); console.log(` GET /webhook/btcpay/test - Test endpoint`); console.log(` GET /health - Health check`); console.log(`\n🔧 Setup in Mattermost:`); console.log(` Trigger: !btcpay`); console.log(` URL: http://localhost:${PORT}/webhook/btcpay`); console.log(` Token: ${config.mattermost_token}`); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('🛑 Shutting down webhook server...'); process.exit(0); }); module.exports = app;