Turn an Old Android Phone into a 24/7 Edge Cluster (Node.js, PM2, Hardware Telemetry) - Zero Fluff Guide

Hey r/selfhosted, Most mid-range or flagship Android phones carry octa-core ARM chips, 8–16GB RAM, and high-speed UFS storage, but usually end up sitting idle in a drawer. Instead of spinning up another paid cloud VM or paying for external webhook relays, you can turn an unrooted Android device into an autonomous 24/7 edge cluster using Termux, Node.js, and PM2. Here is the entire zero-placeholder architecture, setup script, non-blocking server code, and background sync setup so you can replicate it directly. Architecture Overview [Android Native Hardware / Ingress APIs] │── termux-clipboard-get ──> /sdcard/Download/Vault_Telemetry/text/ │── termux-microphone-record ──> /sdcard/Download/Vault_Telemetry/audio/ └── termux-camera-photo ──> /sdcard/Download/Vault_Telemetry/video/ │ ▼ [Local Edge Cluster (PM2 Managed)] │── Port 3001: Webhook Ingress Gateway (Payload validation, stream routing) │── Port 3002: Vault Telemetry Storage Engine (NDJSON Ledger Audit) └── Port 3003: Safe-Harbor Automated Triage Engine │ ▼ [Storage & Sovereign Sync] │── Local State: /sdcard/Download/Vault_Telemetry/telemetry_ledger.ndjson └── Cloud Archive: rclone sync -> sovereign_drive:Termux_Enterprise_Archive Step 1: Prerequisites & Package Setup Make sure you are using Termux from F-Droid (not Google Play) and have installed the companion Termux:API app from F-Droid as well. Open Termux and install the core toolchain: pkg update -y && pkg upgrade -y pkg install -y nodejs-lts termux-api curl jq rclone npm install -g pm2 Grant storage permissions so Termux can write telemetry data: termux-setup-storage (Accept the Android storage permission prompt when it appears). Step 2: Initialize Directory Structure & Vault Run this script to initialize the isolated storage vault and seed the append-only ledger: mkdir -p /sdcard/Download/Vault_Telemetry/text mkdir -p /sdcard/Download/Vault_Telemetry/audio mkdir -p /sdcard/Download/Vault_Telemetry/video mkdir -p "$HOME/edge_cluster" LEDGER="/sdcard/Download/Vault_Telemetry/telemetry_ledger.ndjson" if [ ! -f "$LEDGER" ]; then echo '{"system":"Sovereign_Edge_Node","initialized":"'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'","status":"ACTIVE"}' > "$LEDGER" fi echo "[✓] Vault file tree and ledger initialized." Step 3: Edge Gateway Microservices (gateway.js) Save this file to $HOME/edge_cluster/gateway.js. It runs three microservices across ports 3001, 3002, and 3003 using zero external npm dependencies. To prevent event-loop latency spikes and file-lock corruption on Android storage, this implementation uses non-blocking append-only streams (fs.appendFile) in NDJSON format: const http = require('http'); const fs = require('fs'); const PORT_INGRESS = 3001; const PORT_VAULT = 3002; const PORT_TRIAGE = 3003; const LEDGER_PATH = '/sdcard/Download/Vault_Telemetry/telemetry_ledger.ndjson'; // Atomic append-only ledger logging to eliminate event-loop blocking function appendToLedger(entryType, payload, callback) { const record = JSON.stringify({ timestamp: new Date().toISOString(), type: entryType, payload: payload }) + '\n'; fs.appendFile(LEDGER_PATH, record, 'utf8', (err) => { if (err) { console.error(Ledger Write Error: ${err.message}); if (callback) callback(false); return; } if (callback) callback(true); }); } // 1. Ingress Service (Port 3001) - Webhooks & Event Intake const ingressServer = http.createServer((req, res) => { if (req.method === 'POST' && req.url === '/ingress') { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { try { const parsed = JSON.parse(body || '{}'); appendToLedger('INGRESS_EVENT', parsed, (success) => { if (success) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'SUCCESS', node: 'EDGE_PORT_3001' })); } else { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'LEDGER_WRITE_FAILED' })); } }); } catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'INVALID_JSON_PAYLOAD' })); } }); } else { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ service: 'Ingress_Gateway', port: PORT_INGRESS, health: 'ONLINE' })); } }); // 2. Vault Telemetry Service (Port 3002) - Stream-based Ledger Ingestion & Query const vaultServer = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/ledger') { if (!fs.existsSync(LEDGER_PATH)) { res.writeHead(404, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'LEDGER_NOT_FOUND' })); } res.writeHead(200, { 'Content-Type': 'application/x-ndjson' }); const readStream = fs.createReadStream(LEDGER_PATH); readStream.pipe(res); } else { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ service: 'Vault_Storage_Engine', port: PORT_VAULT, health: 'ONLINE' })); } }); // 3. Safe-Harbor Triage Engine (Port 3003) - Automated Routing & Validation const triageServer = http.createServer((req, res) => { if (req.method === 'POST' && req.url === '/triage') { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { try { const ticket = JSON.parse(body || '{"inquiry":"general"}'); const responsePayload = { ticket_id: 'TCK-' + Date.now(), status: 'TRIAGED', policy: 'SAFE_HARBOR_COMPLIANT', retention_units_verified: true, routing: 'AUTOMATED_SUPPORT_TIER_1' }; appendToLedger('TRIAGE_AUDIT', { ticket, resolution: responsePayload }, () => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(responsePayload)); }); } catch (err) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'MALFORMED_TRIAGE_PAYLOAD' })); } }); } else { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ service: 'Safe_Harbor_Triage', port: PORT_TRIAGE, health: 'ONLINE' })); } }); ingressServer.listen(PORT_INGRESS, '127.0.0.1', () => console.log([3001] Ingress online.)); vaultServer.listen(PORT_VAULT, '127.0.0.1', () => console.log([3002] Vault online.)); triageServer.listen(PORT_TRIAGE, '127.0.0.1', () => console.log([3003] Triage online.)); Step 4: Hardware Ingestion & Sync Hook Save this script as $HOME/edge_cluster/telemetry_intake.sh: #!/data/data/com.termux/files/usr/bin/bash set -euo pipefail VAULT_DIR="/sdcard/Download/Vault_Telemetry" TIMESTAMP=$(date +%s) # Capture clipboard content into the vault via Termux:API CLIP_CONTENT=$(termux-clipboard-get 2>/dev/null || true) if [ -n "$CLIP_CONTENT" ]; then echo "$CLIP_CONTENT" > "$VAULT_DIR/text/clip_${TIMESTAMP}.txt" curl -s -X POST 127.0.0.1:3001/ingress \ -H "Content-Type: application/json" \ -d "{\"event\":\"CLIPBOARD_INTAKE\",\"file\":\"clip_${TIMESTAMP}.txt\"}" > /dev/null fi # Offsite backup via rclone (if remote is configured) if command -v rclone &> /dev/null && rclone listremotes | grep -q "^sovereign_drive:"; then rclone sync "$VAULT_DIR" sovereign_drive:Termux_Enterprise_Archive \ --transfers 2 \ --log-level NOTICE \ --exclude "video/**" || true fi Make it executable: chmod +x $HOME/edge_cluster/telemetry_intake.sh Step 5: Background Persistence with PM2 Create $HOME/edge_cluster/ecosystem.config.js: module.exports = { apps: [ { name: "edge-gateway", script: "./gateway.js", cwd: "/data/data/com.termux/files/home/edge_cluster", watch: false, max_memory_restart: "150M", env: { NODE_ENV: "production" } } ] }; Launch the daemon and secure Termux against Android OEM battery killers: cd $HOME/edge_cluster pm2 start ecosystem.config.js pm2 save # Prevent Android battery manager from killing the process in the background termux-wake-lock Verification & Resource Benchmarks Verify all microservice endpoints: curl -s 127.0.0.1:3001 | jq . curl -s 127.0.0.1:3002 | jq . curl -s 127.0.0.1:3003 | jq . Trigger a hardware ingestion event and inspect the ledger: bash $HOME/edge_cluster/telemetry_intake.sh curl -s 127.0.0.1:3002/ledger…

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论