Initial commit of LittleShop project (excluding large archives)
- 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>
This commit is contained in:
278
Hostinger/mattermost_ssh_webhook.js
Normal file
278
Hostinger/mattermost_ssh_webhook.js
Normal file
@@ -0,0 +1,278 @@
|
||||
#!/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;
|
||||
Reference in New Issue
Block a user