- BTCPay Server integration - TeleBot Telegram bot - Review system - Admin area - Docker deployment configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
344 lines
13 KiB
JavaScript
344 lines
13 KiB
JavaScript
#!/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 = `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>BTCPay Server Health - ${config.domain}</title>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
|
|
.container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
|
.status { color: #28a745; font-weight: bold; }
|
|
.onion { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 10px 0; word-break: break-all; }
|
|
.section { margin: 20px 0; padding: 15px; border-left: 4px solid #007bff; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔒 BTCPay Server Health Status</h1>
|
|
<p><strong>Domain:</strong> ${config.domain}</p>
|
|
<p><strong>Status:</strong> <span class="status">✅ OPERATIONAL</span></p>
|
|
<p><strong>Last Updated:</strong> ${new Date().toLocaleString()}</p>
|
|
|
|
<div class="section">
|
|
<h2>🌐 Access Points</h2>
|
|
<p><strong>Clearnet:</strong> <a href="https://${config.domain}">https://${config.domain}</a></p>
|
|
<p><strong>Health Dashboard:</strong> <a href="https://health.${config.domain}">https://health.${config.domain}</a></p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>🧅 Tor Hidden Services</h2>
|
|
<p><strong>BTCPay Server:</strong></p>
|
|
<div class="onion">${btcpayOnion || '⏳ Generating...'}</div>
|
|
<p><strong>Bitcoin P2P Node:</strong></p>
|
|
<div class="onion">${bitcoinOnion || '⏳ Generating...'}</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>📊 System Information</h2>
|
|
<p><strong>Disk Usage:</strong> ${diskUsage}</p>
|
|
<p><strong>Bitcoin Mode:</strong> Pruned (10GB maximum)</p>
|
|
<p><strong>Network:</strong> Tor-only Bitcoin connections</p>
|
|
<p><strong>Security:</strong> Hardened Debian 13</p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>⚡ API Integration</h2>
|
|
<p><strong>REST API:</strong> <code>https://${config.domain}/api</code></p>
|
|
<p><strong>Tor API:</strong> <code>http://${btcpayOnion || 'pending'}/api</code></p>
|
|
<p><strong>Webhooks:</strong> <code>https://${config.domain}/webhook</code></p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>🤖 Mattermost Integration</h2>
|
|
<p><strong>Bot Account:</strong> bankofdebbie</p>
|
|
<p><strong>Commands:</strong> !btcpay, !btcpay onion, !btcpay status</p>
|
|
<p><strong>Webhook URL:</strong> <code>https://health.${config.domain}/webhook</code></p>
|
|
<p><strong>Info API:</strong> <code>https://health.${config.domain}/info</code></p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
|
|
res.send(html);
|
|
} catch (error) {
|
|
res.status(500).send(`<h1>Error</h1><p>${error.message}</p>`);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 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; |