$ -weight: 600;">sudo -weight: 500;">npm -weight: 500;">install -g pm2
-weight: 600;">sudo -weight: 500;">npm -weight: 500;">install -g pm2
-weight: 600;">sudo -weight: 500;">npm -weight: 500;">install -g pm2
pm2 --version
pm2 --version
pm2 --version
mkdir -p ~/projects/automation-bot
cd ~/projects/automation-bot
-weight: 500;">npm init -y
mkdir -p ~/projects/automation-bot
cd ~/projects/automation-bot
-weight: 500;">npm init -y
mkdir -p ~/projects/automation-bot
cd ~/projects/automation-bot
-weight: 500;">npm init -y
-weight: 500;">npm -weight: 500;">install express axios dotenv
-weight: 500;">npm -weight: 500;">install express axios dotenv
-weight: 500;">npm -weight: 500;">install express axios dotenv
cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/automation
LOG_LEVEL=info
EOF
cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/automation
LOG_LEVEL=info
EOF
cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/automation
LOG_LEVEL=info
EOF
const express = require('express');
const axios = require('axios');
require('dotenv').config(); const app = express();
const PORT = process.env.PORT || 3001; app.use(express.json()); // Health check endpoint
app.get('/health', (req, res) => { res.-weight: 500;">status(200).json({ -weight: 500;">status: 'ok', timestamp: new Date().toISOString() });
}); // Webhook endpoint
app.post('/webhook/event', async (req, res) => { try { const { event_type, data } = req.body; console.log(`[${new Date().toISOString()}] Received event: ${event_type}`); // Send to n8n webhook const response = await axios.post(process.env.N8N_WEBHOOK_URL, { event_type, data, received_at: new Date().toISOString(), server_hostname: require('os').hostname() }); res.-weight: 500;">status(200).json({ success: true, message: 'Event processed', n8n_response: response.-weight: 500;">status }); } catch (error) { console.error(`[ERROR] ${error.message}`); res.-weight: 500;">status(500).json({ success: false, error: error.message }); }
}); // Error handling middleware
app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.-weight: 500;">status(500).json({ error: 'Internal server error' });
}); app.listen(PORT, () => { console.log(`[${new Date().toISOString()}] Server running on port ${PORT}`); console.log(`Health check: http://localhost:${PORT}/health`);
}); // Graceful shutdown
process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully'); process.exit(0);
});
const express = require('express');
const axios = require('axios');
require('dotenv').config(); const app = express();
const PORT = process.env.PORT || 3001; app.use(express.json()); // Health check endpoint
app.get('/health', (req, res) => { res.-weight: 500;">status(200).json({ -weight: 500;">status: 'ok', timestamp: new Date().toISOString() });
}); // Webhook endpoint
app.post('/webhook/event', async (req, res) => { try { const { event_type, data } = req.body; console.log(`[${new Date().toISOString()}] Received event: ${event_type}`); // Send to n8n webhook const response = await axios.post(process.env.N8N_WEBHOOK_URL, { event_type, data, received_at: new Date().toISOString(), server_hostname: require('os').hostname() }); res.-weight: 500;">status(200).json({ success: true, message: 'Event processed', n8n_response: response.-weight: 500;">status }); } catch (error) { console.error(`[ERROR] ${error.message}`); res.-weight: 500;">status(500).json({ success: false, error: error.message }); }
}); // Error handling middleware
app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.-weight: 500;">status(500).json({ error: 'Internal server error' });
}); app.listen(PORT, () => { console.log(`[${new Date().toISOString()}] Server running on port ${PORT}`); console.log(`Health check: http://localhost:${PORT}/health`);
}); // Graceful shutdown
process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully'); process.exit(0);
});
const express = require('express');
const axios = require('axios');
require('dotenv').config(); const app = express();
const PORT = process.env.PORT || 3001; app.use(express.json()); // Health check endpoint
app.get('/health', (req, res) => { res.-weight: 500;">status(200).json({ -weight: 500;">status: 'ok', timestamp: new Date().toISOString() });
}); // Webhook endpoint
app.post('/webhook/event', async (req, res) => { try { const { event_type, data } = req.body; console.log(`[${new Date().toISOString()}] Received event: ${event_type}`); // Send to n8n webhook const response = await axios.post(process.env.N8N_WEBHOOK_URL, { event_type, data, received_at: new Date().toISOString(), server_hostname: require('os').hostname() }); res.-weight: 500;">status(200).json({ success: true, message: 'Event processed', n8n_response: response.-weight: 500;">status }); } catch (error) { console.error(`[ERROR] ${error.message}`); res.-weight: 500;">status(500).json({ success: false, error: error.message }); }
}); // Error handling middleware
app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.-weight: 500;">status(500).json({ error: 'Internal server error' });
}); app.listen(PORT, () => { console.log(`[${new Date().toISOString()}] Server running on port ${PORT}`); console.log(`Health check: http://localhost:${PORT}/health`);
}); // Graceful shutdown
process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully'); process.exit(0);
});
pm2 -weight: 500;">start app.js --name "webhook-bot"
pm2 -weight: 500;">start app.js --name "webhook-bot"
pm2 -weight: 500;">start app.js --name "webhook-bot"
βββββββ¬βββββββββββββββ¬ββββββββββββββ¬βββββββ¬βββββββββ¬βββββββββββ
β id β name β namespace β mode β -weight: 500;">status β -weight: 500;">restart β
βββββββΌβββββββββββββββΌββββββββββββββΌβββββββΌβββββββββΌβββββββββββ€
β 0 β webhook-bot β default β fork β online β 0 β
βββββββ΄βββββββββββββββ΄ββββββββββββββ΄βββββββ΄βββββββββ΄βββββββββββ
βββββββ¬βββββββββββββββ¬ββββββββββββββ¬βββββββ¬βββββββββ¬βββββββββββ
β id β name β namespace β mode β -weight: 500;">status β -weight: 500;">restart β
βββββββΌβββββββββββββββΌββββββββββββββΌβββββββΌβββββββββΌβββββββββββ€
β 0 β webhook-bot β default β fork β online β 0 β
βββββββ΄βββββββββββββββ΄ββββββββββββββ΄βββββββ΄βββββββββ΄βββββββββββ
βββββββ¬βββββββββββββββ¬ββββββββββββββ¬βββββββ¬βββββββββ¬βββββββββββ
β id β name β namespace β mode β -weight: 500;">status β -weight: 500;">restart β
βββββββΌβββββββββββββββΌββββββββββββββΌβββββββΌβββββββββΌβββββββββββ€
β 0 β webhook-bot β default β fork β online β 0 β
βββββββ΄βββββββββββββββ΄ββββββββββββββ΄βββββββ΄βββββββββ΄βββββββββββ
-weight: 500;">curl -X POST http://localhost:3001/webhook/event \ -H "Content-Type: application/json" \ -d '{"event_type":"test","data":{"message":"hello"}}'
-weight: 500;">curl -X POST http://localhost:3001/webhook/event \ -H "Content-Type: application/json" \ -d '{"event_type":"test","data":{"message":"hello"}}'
-weight: 500;">curl -X POST http://localhost:3001/webhook/event \ -H "Content-Type: application/json" \ -d '{"event_type":"test","data":{"message":"hello"}}'
module.exports = { apps: [ { name: 'webhook-bot', script: './app.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3001 }, merge_logs: true, autorestart: true, watch: false, max_memory_restart: '500M', error_file: './logs/err.log', out_file: './logs/out.log', log_date_format: 'YYYY-MM-DD HH:mm:ss Z' } ]
};
module.exports = { apps: [ { name: 'webhook-bot', script: './app.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3001 }, merge_logs: true, autorestart: true, watch: false, max_memory_restart: '500M', error_file: './logs/err.log', out_file: './logs/out.log', log_date_format: 'YYYY-MM-DD HH:mm:ss Z' } ]
};
module.exports = { apps: [ { name: 'webhook-bot', script: './app.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3001 }, merge_logs: true, autorestart: true, watch: false, max_memory_restart: '500M', error_file: './logs/err.log', out_file: './logs/out.log', log_date_format: 'YYYY-MM-DD HH:mm:ss Z' } ]
};
pm2 -weight: 500;">stop webhook-bot
pm2 -weight: 500;">start ecosystem.config.js
pm2 -weight: 500;">stop webhook-bot
pm2 -weight: 500;">start ecosystem.config.js
pm2 -weight: 500;">stop webhook-bot
pm2 -weight: 500;">start ecosystem.config.js
βββββββ¬βββββββββββββββ¬ββββββββββββββ¬βββββββββββ¬βββββββββ¬βββββββββββ
β id β name β namespace β mode β -weight: 500;">status β -weight: 500;">restart β
βββββββΌβββββββββββββββΌββββββββββββββΌβββββββββββΌβββββββββΌβββββββββββ€
β 0 β webhook-bot β default β cluster β online β 0 β
β 1 β webhook-bot β default β cluster β online β 0 β
β 2 β webhook-bot β default β cluster β online β 0 β
β 3 β webhook-bot β default β cluster β online β 0 β
βββββββ΄βββββββββββββββ΄ββββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
βββββββ¬βββββββββββββββ¬ββββββββββββββ¬βββββββββββ¬βββββββββ¬βββββββββββ
β id β name β namespace β mode β -weight: 500;">status β -weight: 500;">restart β
βββββββΌβββββββββββββββΌββββββββββββββΌβββββββββββΌβββββββββΌβββββββββββ€
β 0 β webhook-bot β default β cluster β online β 0 β
β 1 β webhook-bot β default β cluster β online β 0 β
β 2 β webhook-bot β default β cluster β online β 0 β
β 3 β webhook-bot β default β cluster β online β 0 β
βββββββ΄βββββββββββββββ΄ββββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
βββββββ¬βββββββββββββββ¬ββββββββββββββ¬βββββββββββ¬βββββββββ¬βββββββββββ
β id β name β namespace β mode β -weight: 500;">status β -weight: 500;">restart β
βββββββΌβββββββββββββββΌββββββββββββββΌβββββββββββΌβββββββββΌβββββββββββ€
β 0 β webhook-bot β default β cluster β online β 0 β
β 1 β webhook-bot β default β cluster β online β 0 β
β 2 β webhook-bot β default β cluster β online β 0 β
β 3 β webhook-bot β default β cluster β online β 0 β
βββββββ΄βββββββββββββββ΄ββββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
pm2 logs webhook-bot
pm2 logs webhook-bot
pm2 logs webhook-bot
pm2 logs webhook-bot --lines 100
pm2 logs webhook-bot --lines 100
pm2 logs webhook-bot --lines 100
tail -f ~/projects/automation-bot/logs/out.log
tail -f ~/projects/automation-bot/logs/out.log
tail -f ~/projects/automation-bot/logs/out.log
pm2 -weight: 500;">install pm2-logrotate
pm2 -weight: 500;">install pm2-logrotate
pm2 -weight: 500;">install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 startup
pm2 startup
pm2 startup
-weight: 600;">sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu
-weight: 600;">sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu
-weight: 600;">sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu
-weight: 600;">sudo reboot
-weight: 600;">sudo reboot
-weight: 600;">sudo reboot
-weight: 500;">systemctl -weight: 500;">status pm2-ubuntu
-weight: 500;">systemctl -weight: 500;">status pm2-ubuntu
-weight: 500;">systemctl -weight: 500;">status pm2-ubuntu
cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-domain.com/webhook/
cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-domain.com/webhook/
cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-domain.com/webhook/ - n8n Cloud or self-hosted n8n (for orchestrating automated workflows)
- Hetzner VPS or Contabo VPS for hosting your Node.js processes
- DigitalOcean as an alternative hosting provider
- Node.js 18+ installed locally and on your server
- SSH access to your VPS
- PM2 (we'll -weight: 500;">install this together)
- A text editor (VS Code recommended) - Why PM2 Matters for Bot Automation
- Installation & Initial Setup
- Running Your First Process
- Clustering & Load Balancing
- Monitoring & Logs in Real-Time
- Auto-Restart on Server Reboot
- Integration with n8n Webhooks
- Getting Started - Restarts crashed processes automatically (no manual intervention)
- Clusters your app across CPU cores (load balancing built-in)
- Manages logs (no more digging through syslog)
- Survives server reboots (auto--weight: 500;">start on startup)
- Monitors memory and CPU (alerts when things go wrong)
- Handles zero-downtime deployments (reload without dropping connections)