#!/usr/bin/env node /** * =============================================================================== * MATTERMOST LOCAL API FOR BTCPAY SSH COMMANDS * =============================================================================== * Created: September 10, 2025 * Purpose: Local web API that runs SSH commands to retrieve BTCPay onion addresses * Deploy: On your Mattermost server (not the VPS) * Usage: Mattermost slash commands → Local API → SSH to VPS → Return data */ const express = require('express'); const { exec } = require('child_process'); const path = require('path'); const fs = require('fs'); const app = express(); const PORT = process.env.PORT || 3333; // Configuration - ADJUST THESE PATHS FOR YOUR MATTERMOST SERVER const config = { vps_domain: 'thebankofdebbie.giize.com', vps_port: 2255, vps_user: 'sysadmin', ssh_key_path: '/mnt/c/Production/Source/LittleShop/Hostinger/vps_hardening_key', mattermost_token: '7grgg4r7sjf4dx9qxa7wuybmnh', allowed_users: ['bankofdebbie', 'admin', 'sysadmin'] }; app.use(express.json()); app.use(express.urlencoded({ extended: true })); /** * Execute SSH command to VPS */ function executeSSHCommand(command) { return new Promise((resolve, reject) => { const sshCmd = `ssh -i ${config.ssh_key_path} -p ${config.vps_port} -o StrictHostKeyChecking=no -o ConnectTimeout=15 ${config.vps_user}@${config.vps_domain} "${command}"`; console.log(`Executing SSH command: ${command}`); exec(sshCmd, { timeout: 30000 }, (error, stdout, stderr) => { if (error) { console.error(`SSH Error: ${error.message}`); reject(new Error(`SSH command failed: ${error.message}`)); return; } if (stderr) { console.warn(`SSH Warning: ${stderr}`); } resolve(stdout.trim()); }); }); } /** * Get BTCPay onion address */ async function getBTCPayOnion() { try { const result = await executeSSHCommand('sudo cat /var/lib/docker/volumes/generated_tor_servicesdir/_data/BTCPayServer/hostname 2>/dev/null || echo "pending"'); return result || 'pending'; } catch (error) { return 'error: ' + error.message; } } /** * Get Bitcoin P2P onion address */ async function getBitcoinOnion() { try { const result = await executeSSHCommand('sudo cat /var/lib/docker/volumes/generated_tor_servicesdir/_data/BTC-P2P/hostname 2>/dev/null || echo "pending"'); return result || 'pending'; } catch (error) { return 'error: ' + error.message; } } /** * Get system status */ async function getSystemStatus() { try { const commands = [ 'docker ps --format "table {{.Names}}\\t{{.Status}}" | grep -E "(btcpay|bitcoin|tor)" | wc -l', 'df -h / | grep -v Filesystem | awk "{print \\$3 \\" used / \\" \\$2 \\" total\\"}"', 'docker logs btcpayserver_bitcoind 2>&1 | grep -i "prune configured" | tail -1 | grep -o "[0-9]* MiB" || echo "10000 MiB"' ]; const [containers, disk, pruning] = await Promise.all( commands.map(cmd => executeSSHCommand(cmd).catch(err => 'error')) ); return { containers: containers + ' containers running', disk_usage: disk, bitcoin_pruning: pruning + ' max storage' }; } catch (error) { return { error: error.message }; } } /** * Main Mattermost slash command endpoint */ app.post('/btcpay', async (req, res) => { try { console.log('Mattermost request:', JSON.stringify(req.body, null, 2)); const { token, user_name, text, command } = req.body; // Validate token if (token !== config.mattermost_token) { return res.json({ response_type: 'ephemeral', text: '❌ Unauthorized: Invalid token' }); } // Check if user is authorized if (!config.allowed_users.includes(user_name)) { return res.json({ response_type: 'ephemeral', text: `❌ Access denied for user: ${user_name}. Contact admin for BTCPay access.` }); } const commandText = (text || '').toLowerCase().trim(); const isOnionCommand = commandText.includes('onion') || commandText === '' || commandText.includes('addresses'); const isStatusCommand = commandText.includes('status'); const isHelpCommand = commandText.includes('help'); if (isHelpCommand) { return res.json({ response_type: 'ephemeral', text: `## BTCPay Server Commands\n\n` + `**Available commands:**\n` + `• \`/btcpay\` or \`/btcpay onion\` - Get onion addresses\n` + `• \`/btcpay status\` - Get system status\n` + `• \`/btcpay help\` - Show this help\n\n` + `**VPS:** ${config.vps_domain}\n` + `**Method:** SSH-based secure retrieval\n` + `**User:** ${user_name} ✅` }); } if (isStatusCommand) { // Get full system status const [btcpayOnion, bitcoinOnion, systemStatus] = await Promise.all([ getBTCPayOnion(), getBitcoinOnion(), getSystemStatus() ]); const response = { response_type: 'in_channel', text: `## 📊 BTCPay Server Status Report\n\n` + `**🌐 Domain:** https://${config.vps_domain}\n\n` + `**🧅 Tor Onion Services:**\n` + `• **BTCPay:** \`${btcpayOnion}\`\n` + `• **Bitcoin P2P:** \`${bitcoinOnion}\`\n\n` + `**📊 System Health:**\n` + `• **Containers:** ${systemStatus.containers || 'checking...'}\n` + `• **Storage:** ${systemStatus.disk_usage || 'checking...'}\n` + `• **Bitcoin:** ${systemStatus.bitcoin_pruning || 'Pruned mode'}\n\n` + `**🔒 Security:** Tor-only Bitcoin, Hardened Debian 13\n` + `**📅 Retrieved:** ${new Date().toLocaleString()}\n` + `**👤 Requested by:** ${user_name}` }; return res.json(response); } if (isOnionCommand) { // Get onion addresses only const [btcpayOnion, bitcoinOnion] = await Promise.all([ getBTCPayOnion(), getBitcoinOnion() ]); const response = { response_type: 'in_channel', text: `## 🧅 BTCPay Tor Onion Addresses\n\n` + `**🌐 Domain:** https://${config.vps_domain}\n\n` + `**🧅 Tor Hidden Services:**\n` + `• **BTCPay Server:** \`${btcpayOnion}\`\n` + `• **Bitcoin P2P:** \`${bitcoinOnion}\`\n\n` + `**🔐 Access Methods:**\n` + `• **Clearnet:** https://${config.vps_domain}\n` + `• **Tor Browser:** http://${btcpayOnion}\n\n` + `**⚡ API Endpoints:**\n` + `• **REST API:** https://${config.vps_domain}/api\n` + `• **Tor API:** http://${btcpayOnion}/api\n\n` + `**📅 Retrieved:** ${new Date().toLocaleString()}\n` + `**👤 Requested by:** ${user_name}` }; return res.json(response); } // Default response return res.json({ response_type: 'ephemeral', text: `❓ Unknown command: "${commandText}"\n\n` + `Use \`/btcpay help\` for available commands.\n\n` + `**Quick commands:**\n` + `• \`/btcpay\` - Get onion addresses\n` + `• \`/btcpay status\` - Get system status` }); } catch (error) { console.error('API Error:', error); return res.json({ response_type: 'ephemeral', text: `❌ **Error retrieving BTCPay information:**\n\`\`\`\n${error.message}\n\`\`\`\n\nPlease check VPS connectivity.` }); } }); /** * Health check endpoint */ app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'Mattermost BTCPay Local API', vps_target: config.vps_domain, method: 'SSH-based commands', timestamp: new Date().toISOString() }); }); /** * Test endpoint */ app.get('/test', async (req, res) => { try { const [btcpayOnion, bitcoinOnion] = await Promise.all([ getBTCPayOnion(), getBitcoinOnion() ]); res.json({ vps_domain: config.vps_domain, btcpay_onion: btcpayOnion, bitcoin_onion: bitcoinOnion, method: 'SSH retrieval', timestamp: new Date().toISOString() }); } catch (error) { res.status(500).json({ error: error.message }); } }); /** * Start server */ app.listen(PORT, '127.0.0.1', () => { console.log(`🚀 Mattermost BTCPay Local API running on localhost:${PORT}`); console.log(`🎯 Target VPS: ${config.vps_domain}:${config.vps_port}`); console.log(`🔑 Method: SSH-based command execution`); console.log(`💡 Endpoints:`); console.log(` POST /btcpay - Mattermost slash command handler`); console.log(` GET /test - Test SSH connectivity`); console.log(` GET /health - Health check`); console.log(`\n🔧 Mattermost Slash Command Setup:`); console.log(` Command: /btcpay`); console.log(` URL: http://localhost:${PORT}/btcpay`); console.log(` Token: ${config.mattermost_token}`); console.log(` Method: POST`); console.log(`\n⚠️ IMPORTANT: Update ssh_key_path in config before running!`); console.log(` Current path: ${config.ssh_key_path}`); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('🛑 Shutting down local API server...'); process.exit(0); }); module.exports = app;