feat(production): sync 10/10 production codebase from demoplace.my.id

This commit is contained in:
rafif committed 2026-07-21 13:30:23 +07:00
commit 0156b84b0e
318 files changed
+56682

No files matched your search

+67
View File
@@ -0,0 +1,67 @@
// scripts/check-agents-error.js
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 80));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function httpGet(url, opts) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 10000, rejectUnauthorized: false, ...opts }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, body: body.substring(0, 500) }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
// 1. Cek error log frontend
await exec(conn, 'tail -50 ' + PUB + '/logs/frontend-error.log 2>/dev/null | head -50', '1. Frontend error log');
// 2. Cek frontend out log terbaru
await exec(conn, 'tail -30 ' + PUB + '/logs/frontend-out.log 2>/dev/null', '2. Frontend out log');
// 3. Test API agents langsung dari server
await exec(conn, 'curl -sk http://127.0.0.1:3001/api/agents 2>/dev/null | head -c 500', '3. Test API /api/agents langsung');
// 4. Test API health backend
await exec(conn, 'curl -sk http://127.0.0.1:3001/api/health 2>/dev/null', '4. Test backend health');
// 5. Cek .env.production - apakah BACKEND_URL benar
await exec(conn, 'cat ' + PUB + '/.env.production 2>/dev/null | grep -v PASSWORD | grep -v SECRET | grep -v MONGO | head -20', '5. Environment variables (safe)');
// 6. Cek apakah backend bisa akses agents collection dari MongoDB
await exec(conn, 'curl -sk http://127.0.0.1:3001/api/agents/list 2>/dev/null | head -c 300', '6. Test /api/agents/list');
conn.end();
// 7. Test dari luar via domain
console.log('\n[7. Test API agents via domain...]');
const r = await httpGet('https://demoplace.my.id/api/agents');
console.log(' Status: ' + r.status);
console.log(' Body: ' + (r.body || r.error || 'empty'));
}
main().catch(e => console.error('[FATAL]', e.message));
+94
View File
@@ -0,0 +1,94 @@
// scripts/check-static-files.js
// Cek apakah static chunk files ada di disk dan bisa diakses via domain
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = {
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
function sshExec(conn, cmd) {
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve('SSH_ERROR'); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
function httpGet(url) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 10000, rejectUnauthorized: false }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, type: res.headers['content-type'], size: body.length }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
// 1. List chunks directory
console.log('=== .next/static/chunks/ contents ===');
await sshExec(conn, `ls -la ${PUB}/.next/static/chunks/ | head -20`);
// 2. Cek CSS files yang direferensikan oleh HTML
console.log('\n=== Cek file CSS spesifik ===');
await sshExec(conn, `ls -la ${PUB}/.next/static/chunks/*.css 2>/dev/null | head -5 || echo "Tidak ada .css di chunks/"`);
// 3. Cek apakah JS bootstrap file ada
console.log('\n=== Cek file JS yang diperlukan ===');
await sshExec(conn, `ls ${PUB}/.next/static/chunks/ | grep -E "(3km6z|21luguo|1-8s9)" | head -10 || echo "File JS tidak ditemukan"`);
// 4. Test akses static file via domain (dari dalam server)
console.log('\n=== Test static file via Apache ===');
await sshExec(conn,
`curl -sk -D - https://demoplace.my.id/_next/static/chunks/3km6z-n6wbgrs.js 2>/dev/null | head -15 || ` +
`curl -sk -D - http://103.185.47.52:8080/_next/static/chunks/3km6z-n6wbgrs.js 2>/dev/null | head -15`
);
// 5. Test apakah Nginx melayani file static langsung
console.log('\n=== Cek Nginx serving static (port 443) ===');
await sshExec(conn,
`curl -sk -w "\\nHTTP: %{http_code} | Type: %{content_type} | Size: %{size_download}" ` +
`-o /dev/null https://demoplace.my.id/_next/static/chunks/3km6z-n6wbgrs.js`
);
// 6. Cek apakah ada .next/server/ directory
console.log('\n=== Status .next/server/ ===');
await sshExec(conn, `ls ${PUB}/.next/server/ 2>/dev/null | head -5 || echo ".next/server/ TIDAK ADA - standalone build mungkin tidak lengkap"`);
conn.end();
// 7. Test dari luar - JS dan CSS
console.log('\n=== Test static resources dari luar ===');
const files = [
'/_next/static/chunks/3km6z-n6wbgrs.js',
'/_next/static/chunks/1u6dw_tm45utz.css',
'/_next/static/chunks/1oskchnhjm4n8.css',
'/_next/static/chunks/21luguo5_g2uu.js',
];
for (const f of files) {
const r = await httpGet(`https://demoplace.my.id${f}`);
const icon = r.status === 200 ? '✅' : '❌';
console.log(` ${icon} ${f}`);
console.log(` HTTP ${r.status} | Type: ${r.type || 'N/A'} | Size: ${r.size || 0} bytes`);
}
}
main().catch(err => console.error('[FATAL]', err.message));
+97
View File
@@ -0,0 +1,97 @@
// scripts/copy-from-backslash.js
// Copy file dari direktori backslash ke path yang benar
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
const SA = PUB + '/.next/standalone/.next/static';
const BSDir = PUB + '/.next\\static\\chunks'; // Backslash literal path
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 90));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function httpGet(url) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 12000, rejectUnauthorized: false }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, size: body.length }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
// 1. List isi direktori backslash
await exec(conn, "ls '" + BSDir + "' | grep css | head -10", '1. CSS di direktori backslash');
// 2. Copy semua file dari direktori backslash ke standalone/chunks
await exec(conn,
"cp '" + BSDir + "/'* '" + SA + "/chunks/' 2>&1 && echo 'COPY OK' || echo 'COPY PARTIAL'",
'2. Copy semua file dari backslash dir ke standalone/chunks'
);
// 3. Verifikasi CSS yang hilang sudah ada
await exec(conn,
"ls '" + SA + "/chunks/' | grep '1oskchnhjm4n8'",
'3. Verifikasi 1oskchnhjm4n8.css ada'
);
// 4. Juga cek nested static dir (ada static/static/)
await exec(conn,
"ls '" + SA + "/static/' 2>/dev/null | head -10 || echo 'Tidak ada nested static'",
'4. Cek nested static dir'
);
// 5. Cek juga backslash css dir (beda dengan chunks)
await exec(conn,
"ls '" + PUB + "/.next\\static\\css' 2>/dev/null | head -10 || echo 'Tidak ada backslash css dir'",
'5. Cek backslash css dir'
);
// 6. Copy CSS dari backslash css dir juga jika ada
await exec(conn,
"[ -d '" + PUB + "/.next\\static\\css' ] && cp '" + PUB + "/.next\\static\\css/'* '" + SA + "/css/' 2>&1 && echo 'CSS copy OK' || echo 'Tidak ada backslash css dir'",
'6. Copy dari backslash css dir'
);
conn.end();
// 7. Final test
console.log('\n[7. Final test semua static files...]');
await new Promise(r => setTimeout(r, 2000));
const files = [
'/_next/static/chunks/1oskchnhjm4n8.css',
'/_next/static/chunks/1u6dw_tm45utz.css',
'/_next/static/chunks/3km6z-n6wbgrs.js',
'/_next/static/chunks/21luguo5_g2uu.js',
'/_next/static/chunks/1-8s9_t85wwr4.js',
'/login',
];
for (const f of files) {
const r = await httpGet('https://demoplace.my.id' + f);
const icon = r.status === 200 ? '✅' : '❌';
console.log(' ' + icon + ' HTTP ' + r.status + ' | ' + (r.size || 0) + ' bytes | ' + f);
}
console.log('\n✅ Selesai! Hard refresh browser (Ctrl+Shift+R)\n');
}
main().catch(e => console.error('[FATAL]', e.message));
+48
View File
@@ -0,0 +1,48 @@
// scripts/deep-diag.js
'use strict';
const { Client } = require('ssh2');
const conn = new Client();
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
function exec(conn, cmd) {
console.log('\n$ ' + cmd.substring(0, 80));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
conn.on('ready', async () => {
// 1. Cek proses yang sedang berjalan
await exec(conn, 'ps -p 3193322 -o cmd 2>/dev/null | tail -1');
// 2. Cek BUILD_ID di standalone
const saPath = PUB + '/.next/standalone';
await exec(conn, 'cat ' + saPath + '/.next/BUILD_ID 2>/dev/null || echo NO_BUILD_ID_IN_STANDALONE');
// 3. List chunks di standalone
await exec(conn, 'ls ' + saPath + '/.next/static/chunks/ | head -15');
// 4. List static root di standalone
await exec(conn, 'ls ' + saPath + '/.next/static/');
// 5. Cek apakah ada server.js lain yang mungkin sedang berjalan
await exec(conn, 'ps aux | grep "node.*server" | grep -v grep | head -5');
// 6. PM2 info untuk backone-frontend
await exec(conn, '/home/adminbackend/.npm-global/bin/pm2 show backone-frontend 2>/dev/null | grep -E "(script|cwd|pid|restart)"');
// 7. Cek log terbaru
await exec(conn, 'tail -20 ' + PUB + '/logs/frontend-out.log 2>/dev/null | head -20');
conn.end();
}).connect({
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 30000
});
+206
View File
@@ -0,0 +1,206 @@
// scripts/deploy-php-proxy.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Deploy PHP proxy ke public_html
// Solusi 502: Apache PHP-FPM forward semua request ke Next.js port 3000
// Tidak butuh mod_proxy_http, root access, atau Hestia API
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client } = require('ssh2');
const SftpClient = require('ssh2-sftp-client');
const https = require('https');
const path = require('path');
const SSH_CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
// ── PHP Proxy script ──────────────────────────────────────────────────────
const PHP_PROXY = `<?php
/**
* BackOne DPI — PHP Reverse Proxy
* Meneruskan semua request HTTP ke Next.js yang berjalan di port 3000
* File ini dikelola otomatis. Jangan dihapus.
*/
// Target: Next.js port 3000
define('NEXTJS_URL', 'http://127.0.0.1:3000');
define('TIMEOUT', 30);
// Ambil URI request
$requestUri = $_SERVER['REQUEST_URI'] ?? '/';
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$target = NEXTJS_URL . $requestUri;
// Kumpulkan headers dari request asli
$forwardHeaders = [
'X-Forwarded-For: ' . ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'),
'X-Forwarded-Proto: https',
'X-Real-IP: ' . ($_SERVER['REMOTE_ADDR'] ?? ''),
'Host: ' . ($_SERVER['HTTP_HOST'] ?? 'demoplace.my.id'),
];
// Tambahkan headers lain dari request
$allHeaders = getallheaders() ?: [];
$skipHeaders = ['host', 'connection', 'transfer-encoding', 'keep-alive', 'content-length'];
foreach ($allHeaders as $name => $value) {
if (!in_array(strtolower($name), $skipHeaders)) {
$forwardHeaders[] = "$name: $value";
}
}
// Body untuk POST/PUT/PATCH
$body = in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE']) ? file_get_contents('php://input') : null;
// Kirim request ke Next.js
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $target,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $forwardHeaders,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => TIMEOUT,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_ENCODING => '',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$curlError = curl_error($ch);
curl_close($ch);
// Jika curl error
if ($response === false) {
http_response_code(502);
header('Content-Type: text/plain');
echo 'BackOne: Gagal terhubung ke Next.js. Error: ' . $curlError;
exit;
}
// Pisah headers dan body
$responseHeaders = substr($response, 0, $headerSize);
$responseBody = substr($response, $headerSize);
// Set HTTP status
http_response_code($httpCode ?: 200);
// Forward response headers ke client
$skipResponseHeaders = ['transfer-encoding', 'connection', 'keep-alive', 'content-encoding'];
foreach (explode("\\r\\n", $responseHeaders) as $header) {
$header = trim($header);
if (empty($header) || preg_match('/^HTTP\\//i', $header)) continue;
$colonPos = strpos($header, ':');
if ($colonPos === false) continue;
$headerName = strtolower(trim(substr($header, 0, $colonPos)));
if (in_array($headerName, $skipResponseHeaders)) continue;
header($header, false);
}
// Kirim body
echo $responseBody;
`;
// ── .htaccess baru: route semua request ke proxy.php ─────────────────────
const HTACCESS = `# BackOne DPI — Apache .htaccess
# Redirect HTTP ke HTTPS
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Route semua request ke PHP proxy (meneruskan ke Next.js port 3000)
RewriteCond %{REQUEST_FILENAME} !proxy.php
RewriteRule ^(.*)$ /proxy.php [L,QSA]
`;
// ── Fungsi upload file via SFTP ───────────────────────────────────────────
async function uploadFile(sftp, content, remotePath) {
const buf = Buffer.from(content, 'utf8');
await sftp.put(buf, remotePath);
console.log(` ✅ Upload: ${remotePath}`);
}
// ── SSH command ────────────────────────────────────────────────────────────
function sshExec(conn, cmd) {
console.log(`\n$ ${cmd.substring(0, 100)}...`);
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '';
stream.on('close', () => resolve(out))
.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); });
});
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Deploy PHP Proxy (Fix 502) ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// ── Upload via SFTP ──────────────────────────────────────────────────────
console.log('▶ Step 1: Upload proxy.php dan .htaccess via SFTP...\n');
const sftp = new SftpClient();
await sftp.connect(SSH_CONFIG);
await uploadFile(sftp, PHP_PROXY, `${ROOT}/proxy.php`);
await uploadFile(sftp, HTACCESS, `${ROOT}/.htaccess`);
await sftp.end();
// ── Verify dan test via SSH ──────────────────────────────────────────────
console.log('\n▶ Step 2: Verifikasi file di server...');
const conn = new Client();
await new Promise((resolve, reject) => { conn.on('ready', resolve).on('error', reject).connect(SSH_CONFIG); });
console.log('[SSH] Terhubung!\n');
await sshExec(conn, `ls -la ${ROOT}/proxy.php ${ROOT}/.htaccess`);
await sshExec(conn, `head -5 ${ROOT}/proxy.php`);
await sshExec(conn, `cat ${ROOT}/.htaccess`);
// ── Test internal Apache response ─────────────────────────────────────
console.log('\n▶ Step 3: Test Apache internal (port 8080)...');
await sshExec(conn, `curl -sk -o /dev/null -w "Apache HTTP: %{http_code}" http://127.0.0.1:8080/`);
await sshExec(conn, `curl -sk -o /dev/null -w "Apache Login: %{http_code}" http://127.0.0.1:8080/login`);
// ── Test domain publik ─────────────────────────────────────────────────
console.log('\n▶ Step 4: Test domain demoplace.my.id...');
await sshExec(conn,
`sleep 2 && curl -sk -o /dev/null -w "HTTPS /: %{http_code}" https://demoplace.my.id/ && ` +
`curl -sk -o /dev/null -w " | HTTPS /login: %{http_code}" https://demoplace.my.id/login`
);
conn.end();
// ── Final test dari luar ───────────────────────────────────────────────
console.log('\n▶ Step 5: Final test dari jaringan lokal...');
await new Promise(r => setTimeout(r, 3000));
for (const path of ['/', '/login']) {
const r = await new Promise(resolve => {
const req = https.get(`https://demoplace.my.id${path}`,
{ timeout: 12000, rejectUnauthorized: false },
res => resolve({ status: res.statusCode, location: res.headers.location })
);
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
const icon = r.status >= 200 && r.status < 400 ? '✅' : r.status === 0 ? '⚠️ ' : '❌';
console.log(` ${icon} https://demoplace.my.id${path} → HTTP ${r.status} ${r.error || ''}`);
if (r.location) console.log(` → Redirect ke: ${r.location}`);
}
console.log('\n✅ PHP Proxy berhasil di-deploy!\n');
console.log('Cek browser: https://demoplace.my.id/login\n');
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+332
View File
@@ -0,0 +1,332 @@
// scripts/deploy-sftp.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Production SFTP Deployment Script (Windows Compatible)
//
// Usage: node scripts/deploy-sftp.js
// Requires: npm install ssh2-sftp-client ssh2 (already in devDependencies)
//
// This script:
// 1. Verifies the production build exists (.next/standalone)
// 2. Connects to server via SFTP (port 2222)
// 3. Uploads all required files to the remote server
// 4. Runs post-deploy commands via SSH (npm install, PM2 restart)
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const SftpClient = require('ssh2-sftp-client');
const { Client: SshClient } = require('ssh2');
const path = require('path');
const fs = require('fs');
// ─── Configuration ────────────────────────────────────────────────────────────
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
remotePath: '/home/adminbackend/web/demoplace.my.id/public_html',
};
const LOCAL_ROOT = path.resolve(__dirname, '..');
// Files/dirs to upload (relative to LOCAL_ROOT)
// These are the minimum required for production deployment
const UPLOAD_MANIFEST = [
// Next.js standalone build output
{ local: '.next/standalone', remote: '.next/standalone', type: 'dir' },
{ local: '.next/static', remote: '.next/static', type: 'dir' },
{ local: 'public', remote: 'public', type: 'dir' },
// Backend API server
{ local: 'backend/server.js', remote: 'backend/server.js', type: 'file' },
{ local: 'backend/db', remote: 'backend/db', type: 'dir' },
{ local: 'backend/middleware', remote: 'backend/middleware', type: 'dir' },
{ local: 'backend/models', remote: 'backend/models', type: 'dir' },
{ local: 'backend/routes', remote: 'backend/routes', type: 'dir' },
{ local: 'backend/deviceResolver.js', remote: 'backend/deviceResolver.js', type: 'file' },
{ local: 'backend/generate_export.js', remote: 'backend/generate_export.js', type: 'file' },
{ local: 'backend/package.json', remote: 'backend/package.json', type: 'file' },
{ local: 'backend/package-lock.json', remote: 'backend/package-lock.json', type: 'file' },
// Proxy data collector
{ local: 'proxy/index.js', remote: 'proxy/index.js', type: 'file' },
{ local: 'proxy/collector.js', remote: 'proxy/collector.js', type: 'file' },
{ local: 'proxy/collectorHelper.js', remote: 'proxy/collectorHelper.js', type: 'file' },
{ local: 'proxy/collectorHelperDpi.js', remote: 'proxy/collectorHelperDpi.js', type: 'file' },
{ local: 'proxy/collectorHelperDpi2.js', remote: 'proxy/collectorHelperDpi2.js', type: 'file' },
{ local: 'proxy/dataRetention.js', remote: 'proxy/dataRetention.js', type: 'file' },
{ local: 'proxy/db', remote: 'proxy/db', type: 'dir' },
{ local: 'proxy/deviceResolver.js', remote: 'proxy/deviceResolver.js', type: 'file' },
{ local: 'proxy/models', remote: 'proxy/models', type: 'dir' },
{ local: 'proxy/netifyClient.js', remote: 'proxy/netifyClient.js', type: 'file' },
{ local: 'proxy/netifyClientCore.js', remote: 'proxy/netifyClientCore.js', type: 'file' },
{ local: 'proxy/netifyTelemetry.js', remote: 'proxy/netifyTelemetry.js', type: 'file' },
{ local: 'proxy/routes.js', remote: 'proxy/routes.js', type: 'file' },
{ local: 'proxy/scheduler.js', remote: 'proxy/scheduler.js', type: 'file' },
{ local: 'proxy/package.json', remote: 'proxy/package.json', type: 'file' },
{ local: 'proxy/package-lock.json', remote: 'proxy/package-lock.json', type: 'file' },
// PM2 ecosystem config
{ local: 'ecosystem.config.js', remote: 'ecosystem.config.js', type: 'file' },
// Production environment (CONFIDENTIAL — uploaded via SFTP, not git)
{ local: '.env.production', remote: '.env.production', type: 'file' },
];
// Post-deploy commands to run on server via SSH
const POST_DEPLOY_COMMANDS = [
// Ensure required directories exist
'mkdir -p /home/adminbackend/web/demoplace.my.id/public_html/logs',
'mkdir -p /home/adminbackend/web/demoplace.my.id/public_html/api/uploads',
'mkdir -p /home/adminbackend/web/demoplace.my.id/public_html/backend/uploads',
'mkdir -p /home/adminbackend/web/demoplace.my.id/public_html/backend/public/api/uploads',
// Set correct permissions on upload directories
'chmod 755 /home/adminbackend/web/demoplace.my.id/public_html/api/uploads',
'chmod 755 /home/adminbackend/web/demoplace.my.id/public_html/backend/uploads',
'chmod 755 /home/adminbackend/web/demoplace.my.id/public_html/backend/public/api/uploads',
// Install backend dependencies (production only)
'cd /home/adminbackend/web/demoplace.my.id/public_html/backend && npm ci --omit=dev 2>&1 | tail -5',
// Install proxy dependencies (production only)
'cd /home/adminbackend/web/demoplace.my.id/public_html/proxy && npm ci --omit=dev 2>&1 | tail -5',
// Copy Next.js static assets to standalone directory (required for standalone mode)
// Fix standalone directory structure for Next.js
'mkdir -p /home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.next/',
'cp -r /home/adminbackend/web/demoplace.my.id/public_html/.next/static /home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.next/static && echo "Static assets copied to standalone"',
'cp -r /home/adminbackend/web/demoplace.my.id/public_html/public /home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/public 2>/dev/null || true',
// SECURITY: Remove .env files from standalone (they should never be in the build output)
'rm -f /home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.env.production && echo "Removed .env.production from standalone"',
'rm -f /home/adminbackend/web/demoplace.my.id/public_html/.next/standalone/.env.local 2>/dev/null || true',
// Check if PM2 is installed (use local install path)
'export PM2=/home/adminbackend/.npm-global/bin/pm2 && $PM2 --version 2>&1 || (npm config set prefix /home/adminbackend/.npm-global && npm install -g pm2)',
// Reload PM2 processes with new code (zero-downtime reload)
'export PM2=/home/adminbackend/.npm-global/bin/pm2 && cd /home/adminbackend/web/demoplace.my.id/public_html && NODE_ENV=production $PM2 reload ecosystem.config.js 2>&1 || NODE_ENV=production $PM2 start ecosystem.config.js',
// Save PM2 process list for auto-restart on server reboot
'/home/adminbackend/.npm-global/bin/pm2 save',
// Show status
'/home/adminbackend/.npm-global/bin/pm2 list',
// Test health endpoints
'sleep 5 && curl -s http://127.0.0.1:3001/api/health || echo "Backend not ready yet"',
];
// ─── Helper: Upload a directory recursively ───────────────────────────────────
async function uploadDirectory(sftp, localDir, remoteDir, options = {}) {
const localPath = path.join(LOCAL_ROOT, localDir);
if (!fs.existsSync(localPath)) {
console.log(` [SKIP] ${localDir} — does not exist locally`);
return;
}
// Ensure remote directory exists
try {
await sftp.mkdir(path.posix.join(CONFIG.remotePath, remoteDir), true);
} catch {
// Directory may already exist
}
const items = fs.readdirSync(localPath, { withFileTypes: true });
for (const item of items) {
// Only exclude .next and node_modules at the TOP level of the project root.
// Inside .next/standalone/, the .next subdir contains build artifacts and MUST be uploaded.
// node_modules inside standalone are also needed (pruned deps) — skip them; server handles npm ci.
const isTopLevel = !localDir.includes('standalone');
if (isTopLevel && ['node_modules', '.git', '.next'].includes(item.name)) continue;
if (!isTopLevel && ['node_modules', '.git'].includes(item.name)) continue;
// Skip log files
if (item.name.endsWith('.log')) continue;
// Skip local env file from standalone (security — should not be on server in build)
if (item.name === '.env.production' || item.name === '.env.local') continue;
const localItemPath = path.join(localPath, item.name);
const remoteItemPath = path.posix.join(CONFIG.remotePath, remoteDir, item.name);
if (item.isDirectory()) {
try {
await sftp.mkdir(remoteItemPath, true);
} catch {
// Ignore if exists
}
await uploadDirectory(sftp, path.join(localDir, item.name), path.join(remoteDir, item.name), options);
} else {
process.stdout.write(` [UP] ${path.join(remoteDir, item.name)} ... `);
await sftp.put(localItemPath, remoteItemPath);
process.stdout.write('done\n');
}
}
}
// ─── Helper: Run SSH commands ─────────────────────────────────────────────────
function runSshCommands(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
const results = [];
ssh.on('ready', () => {
console.log('\n[SSH] Connected. Running post-deploy commands...\n');
let commandIndex = 0;
function runNext() {
if (commandIndex >= commands.length) {
ssh.end();
return;
}
const cmd = commands[commandIndex++];
console.log(`\n $ ${cmd}`);
ssh.exec(cmd, (err, stream) => {
if (err) {
console.error(` [ERROR] ${err.message}`);
runNext();
return;
}
let output = '';
stream.on('data', (data) => {
output += data;
process.stdout.write(data.toString());
});
stream.stderr.on('data', (data) => {
process.stdout.write(` [stderr] ${data}`);
});
stream.on('close', () => {
results.push({ cmd, output });
runNext();
});
});
}
runNext();
ssh.on('end', () => resolve(results));
});
ssh.on('error', reject);
ssh.connect({
host: CONFIG.host,
port: CONFIG.port,
username: CONFIG.username,
password: CONFIG.password,
readyTimeout: 30000,
});
});
}
// ─── Main Deployment Function ─────────────────────────────────────────────────
async function deploy() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Production Deployment Script ║');
console.log('╚════════════════════════════════════════════════════════╝\n');
// Pre-flight check: verify .next/standalone exists
const standaloneDir = path.join(LOCAL_ROOT, '.next', 'standalone');
if (!fs.existsSync(standaloneDir)) {
console.error('✗ ERROR: .next/standalone not found!');
console.error(' Run: npm run build');
console.error(' Then re-run this deployment script.\n');
process.exit(1);
}
// Pre-flight check: verify .env.production exists
const envProd = path.join(LOCAL_ROOT, '.env.production');
if (!fs.existsSync(envProd)) {
console.error('✗ ERROR: .env.production not found!');
process.exit(1);
}
console.log(`[Config] Host : ${CONFIG.host}:${CONFIG.port}`);
console.log(`[Config] User : ${CONFIG.username}`);
console.log(`[Config] Remote : ${CONFIG.remotePath}`);
console.log(`[Config] Files : ${UPLOAD_MANIFEST.length} items to upload\n`);
// ─── Phase 1: SFTP Upload ─────────────────────────────────────────────────
const sftp = new SftpClient();
try {
console.log('[SFTP] Connecting...');
await sftp.connect({
host: CONFIG.host,
port: CONFIG.port,
username: CONFIG.username,
password: CONFIG.password,
readyTimeout: 30000,
});
console.log('[SFTP] Connected!\n');
// Ensure remote root exists
try {
await sftp.mkdir(CONFIG.remotePath, true);
} catch {
// Already exists
}
let uploaded = 0;
let skipped = 0;
for (const item of UPLOAD_MANIFEST) {
const localPath = path.join(LOCAL_ROOT, item.local);
if (!fs.existsSync(localPath)) {
console.log(`[SKIP] ${item.local} — not found locally`);
skipped++;
continue;
}
if (item.type === 'dir') {
console.log(`\n[DIR] Uploading ${item.local}/`);
await uploadDirectory(sftp, item.local, item.remote);
uploaded++;
} else {
const remotePath = path.posix.join(CONFIG.remotePath, item.remote);
// Ensure parent directory exists
const remoteDir = remotePath.substring(0, remotePath.lastIndexOf('/'));
try {
await sftp.mkdir(remoteDir, true);
} catch {
// Ignore
}
console.log(`[FILE] ${item.local}`);
await sftp.put(localPath, remotePath);
uploaded++;
}
}
console.log(`\n[SFTP] Upload complete! ${uploaded} items uploaded, ${skipped} skipped.`);
await sftp.end();
} catch (err) {
console.error('\n[SFTP] ERROR:', err.message);
try { await sftp.end(); } catch { /* ignore */ }
process.exit(1);
}
// ─── Phase 2: SSH Post-Deploy Commands ───────────────────────────────────
try {
await runSshCommands(POST_DEPLOY_COMMANDS);
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ ✅ Deployment Complete! ║');
console.log('║ ║');
console.log('║ Dashboard : https://demoplace.my.id ║');
console.log('║ Health : http://127.0.0.1:3001/api/health ║');
console.log('╚════════════════════════════════════════════════════════╝\n');
} catch (err) {
console.error('\n[SSH] ERROR:', err.message);
process.exit(1);
}
}
// Run
deploy().catch((err) => {
console.error('\n[DEPLOY] Fatal error:', err);
process.exit(1);
});
+119
View File
@@ -0,0 +1,119 @@
const { Client } = require('ssh2');
const SftpClient = require('ssh2-sftp-client');
const path = require('path');
const fs = require('fs');
const archiver = require('archiver');
const config = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH'
};
const DEPLOY_DIR = 'web/demoplace.my.id/public_html';
async function createZip() {
console.log("Packaging application...");
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(path.join(__dirname, '../deploy.zip'));
const archive = new archiver.ZipArchive({ zlib: { level: 9 } });
output.on('close', () => resolve(path.join(__dirname, '../deploy.zip')));
archive.on('error', (err) => reject(err));
archive.pipe(output);
// Add standalone output
archive.directory(path.join(__dirname, '../.next/standalone'), false);
// Add static assets (Next.js standalone requires these copied over)
archive.directory(path.join(__dirname, '../.next/static'), '.next/static');
archive.directory(path.join(__dirname, '../public'), 'public');
archive.directory(path.join(__dirname, '../public'), false);
// Add backend and proxy
archive.directory(path.join(__dirname, '../backend'), 'backend');
archive.directory(path.join(__dirname, '../proxy'), 'proxy');
// Add PM2 ecosystem config
archive.file(path.join(__dirname, '../ecosystem.config.js'), { name: 'ecosystem.config.js' });
// Add production env
archive.file(path.join(__dirname, '../.env.production'), { name: '.env.production' });
archive.finalize();
});
}
async function runSSH(cmd) {
return new Promise((resolve, reject) => {
const conn = new Client();
conn.on('ready', () => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '';
stream.on('close', (code, signal) => {
conn.end();
resolve(out);
}).on('data', (data) => {
out += data;
}).stderr.on('data', (data) => {
out += data;
});
});
}).on('error', reject).connect(config);
});
}
async function deploy() {
const sftp = new SftpClient();
let zipPath;
try {
zipPath = await createZip();
console.log("Package created successfully.");
console.log("Connecting to SFTP server...");
await sftp.connect(config);
console.log("Creating deployment directory...");
const homeDir = await runSSH('pwd');
const targetDir = `${homeDir.trim()}/${DEPLOY_DIR}`;
await runSSH(`mkdir -p ${targetDir}`);
console.log(`Uploading deploy.zip to ${targetDir}...`);
await sftp.put(zipPath, `${targetDir}/deploy.zip`);
console.log("Extracting package on server...");
await runSSH(`cd ${targetDir} && rm -rf .next && unzip -o deploy.zip && rm deploy.zip`);
console.log("Creating symlink for static files...");
await runSSH(`cd ${targetDir} && rm -rf _next && ln -s .next _next`);
console.log("Installing production dependencies for root, backend & proxy...");
await runSSH(`. ~/.nvm/nvm.sh && nvm use 20 && cd ${targetDir} && npm install --production`);
await runSSH(`. ~/.nvm/nvm.sh && nvm use 20 && cd ${targetDir}/backend && npm install --production`);
await runSSH(`. ~/.nvm/nvm.sh && nvm use 20 && cd ${targetDir}/proxy && npm install --production`);
console.log("Configuring PM2...");
// PM2 ecosystem is now packaged in deploy.zip directly, no need to generate via bash echo.
console.log("Restarting PM2 processes...");
// Clean delete old PM2 processes if they exist to apply new paths, then start fresh
await runSSH(`. ~/.nvm/nvm.sh && nvm use 20 && npx pm2 delete backone-frontend backone-backend backone-proxy || true`);
const pm2Result = await runSSH(`. ~/.nvm/nvm.sh && nvm use 20 && cd ${targetDir} && npx pm2 start ecosystem.config.js`);
console.log(pm2Result);
console.log("Deployment completed successfully!");
console.log(`BackOne Dashboard is now live. Please map your Nginx/Apache domain demoplace.my.id to http://127.0.0.1:3000`);
} catch(e) {
console.error("Deployment failed:", e.message);
} finally {
sftp.end();
if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
}
}
deploy();
+99
View File
@@ -0,0 +1,99 @@
// scripts/diagnose-ports.js
// Cek port apa yang aktif dan test PHP proxy di port yang benar
'use strict';
const { Client } = require('ssh2');
const SSH = {
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
function sshExec(conn, cmd, label = '') {
if (label) console.log(`\n[${label}]`);
console.log(`$ ${cmd.substring(0, 100)}${cmd.length > 100 ? '...' : ''}`);
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '';
stream.on('close', () => resolve(out))
.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); });
});
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Diagnosa Port & PHP Proxy Test ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
const conn = new Client();
await new Promise((resolve, reject) => { conn.on('ready', resolve).on('error', reject).connect(SSH); });
console.log('[SSH] Terhubung!\n');
// 1. Cek semua port yang aktif
await sshExec(conn,
'echo "=== SEMUA PORT AKTIF ===" && ss -tlnp | grep -E "(80|443|3000|3001|4000|8008|8009|8080|8443)"',
'PORT AKTIF'
);
// 2. Cek apakah Apache aktif di berbagai port
await sshExec(conn,
'echo "=== TES APACHE PORT 8009 ===" && curl -sk -o /dev/null -w "Port 8009: %{http_code}" http://127.0.0.1:8009/login',
'TES PORT 8009'
);
await sshExec(conn,
'echo "=== TES APACHE PORT 8080 (IP publik) ===" && curl -sk -o /dev/null -w "Port 8080 publik: %{http_code}" http://103.185.47.52:8080/login',
'TES PORT 8080'
);
// 3. Cek apakah PHP FPM socket ada
await sshExec(conn,
'ls -la /run/php/php*-fpm-demoplace* 2>/dev/null || echo "Tidak ada PHP FPM socket untuk domain ini"',
'PHP FPM SOCKET'
);
// 4. Cek isi .htaccess yang aktif
await sshExec(conn,
'cat /home/adminbackend/web/demoplace.my.id/public_html/.htaccess',
'HTACCESS AKTIF'
);
// 5. Cek apakah proxy.php bisa diakses via Apache
await sshExec(conn,
'curl -sk -v http://127.0.0.1:8009/proxy.php 2>&1 | head -30',
'TES proxy.php via port 8009'
);
// 6. Test direct PHP execution
await sshExec(conn,
'php -r "echo curl_version()[\'version\'];" 2>/dev/null && echo " (PHP curl OK)" || echo "PHP curl tidak tersedia"',
'CEK PHP CURL'
);
// 7. Cek Apache error log
await sshExec(conn,
'tail -20 /var/log/apache2/domains/demoplace.my.id.error.log 2>/dev/null | grep -v "^\$" | tail -10 || echo "Tidak bisa akses error log"',
'APACHE ERROR LOG'
);
// 8. Test Next.js langsung
await sshExec(conn,
'curl -sk -o /dev/null -w "Next.js port 3000 /login: %{http_code}" http://127.0.0.1:3000/login',
'TES NEXT.JS LANGSUNG'
);
// 9. Coba test PHP via curl dengan header Host
await sshExec(conn,
'curl -sk -H "Host: demoplace.my.id" -o /dev/null -w "Apache dengan Host header: %{http_code}" http://127.0.0.1:8009/',
'TES APACHE HOST HEADER'
);
conn.end();
console.log('\n✅ Diagnosa selesai!\n');
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+90
View File
@@ -0,0 +1,90 @@
// scripts/download-production.js
'use strict';
const SftpClient = require('ssh2-sftp-client');
const path = require('path');
const fs = require('fs');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
remotePath: '/home/adminbackend/web/demoplace.my.id/public_html',
};
const LOCAL_ROOT = path.resolve(__dirname, '..');
const DIRS_TO_DOWNLOAD = ['src', 'backend', 'proxy', 'public', 'scripts'];
const FILES_TO_DOWNLOAD = [
'package.json',
'package-lock.json',
'next.config.ts',
'postcss.config.mjs',
'tsconfig.json',
'eslint.config.mjs',
'README.md',
'SKILLS.md',
'Dockerfile',
'docker-compose.yml',
'docker-compose.prod.yml',
'ecosystem.config.js',
'deploy.sh'
];
async function main() {
const sftp = new SftpClient();
try {
console.log(`[SFTP] Connecting to ${CONFIG.host}:${CONFIG.port}...`);
await sftp.connect({
host: CONFIG.host,
port: CONFIG.port,
username: CONFIG.username,
password: CONFIG.password,
});
console.log('[SFTP] Connected successfully.');
// 1. Download directories recursively
for (const dirName of DIRS_TO_DOWNLOAD) {
const remoteDir = `${CONFIG.remotePath}/${dirName}`;
const localDir = path.join(LOCAL_ROOT, dirName);
if (await sftp.exists(remoteDir)) {
console.log(`[SFTP] Downloading directory: ${dirName}...`);
fs.mkdirSync(localDir, { recursive: true });
await sftp.downloadDir(remoteDir, localDir, {
filter: (filePath, isDir) => {
const basename = path.basename(filePath);
return !['node_modules', '.next', '.git', 'logs', 'repo.zip'].includes(basename);
}
});
console.log(`[SFTP] Finished downloading directory: ${dirName}`);
} else {
console.warn(`[SFTP] Remote directory not found: ${remoteDir}`);
}
}
// 2. Download specific root files
for (const fileName of FILES_TO_DOWNLOAD) {
const remoteFile = `${CONFIG.remotePath}/${fileName}`;
const localFile = path.join(LOCAL_ROOT, fileName);
if (await sftp.exists(remoteFile)) {
console.log(`[SFTP] Downloading file: ${fileName}...`);
await sftp.fastGet(remoteFile, localFile);
console.log(`[SFTP] Downloaded: ${fileName}`);
} else {
console.warn(`[SFTP] Remote file not found: ${remoteFile}`);
}
}
console.log('\n✅ [SFTP] Download of production codebase (10/10) completed successfully!');
} catch (err) {
console.error('❌ [SFTP] Download failed:', err);
process.exit(1);
} finally {
await sftp.end();
}
}
main();
+121
View File
@@ -0,0 +1,121 @@
// scripts/final-check.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Final production health check
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const https = require('https');
const http = require('http');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
function fetch(url, followRedirects = false) {
return new Promise(resolve => {
const mod = url.startsWith('https') ? https : http;
const opts = { timeout: 12000, rejectUnauthorized: false };
if (!followRedirects) opts.agent = false;
const req = mod.get(url, opts, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: body.substring(0, 300) }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
function runSsh(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
let i = 0;
function next() {
if (i >= commands.length) { ssh.end(); return; }
const cmd = commands[i++];
console.log(`\n$ ${cmd}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error('[ERR]', err.message); next(); return; }
stream.on('data', d => process.stdout.write(d.toString()));
stream.stderr.on('data', d => { const t = d.toString(); if (!t.includes('npm warn') && !t.includes('notice')) process.stdout.write(t); });
stream.on('close', next);
});
}
next();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Final Production Verification ║');
console.log('╚══════════════════════════════════════════════════════════════╝\n');
// SSH internal checks
await runSsh([
`${PM2} list`,
`echo "=== FRONTEND ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/`,
`echo "=== LOGIN PAGE ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
`echo "=== BACKEND ===" && curl -s http://127.0.0.1:3001/api/health`,
// Test full login flow
`echo "=== LOGIN API ===" && curl -s -X POST http://127.0.0.1:3001/api/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin"}' | python3 -c "import sys,json; d=json.load(sys.stdin); print('Login OK - Role:', d.get('user',{}).get('role','?'))" 2>/dev/null || curl -s -X POST http://127.0.0.1:3001/api/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin"}'`,
// Security checks
`echo "=== SECURITY: Port 3001 ===" && ss -tlnp | grep 3001`,
`echo "=== SECURITY: Port 4000 ===" && ss -tlnp | grep 4000`,
`echo "=== SECURITY: No .env in standalone ===" && find ${ROOT}/.next/standalone -name ".env*" 2>/dev/null || echo "CLEAN"`,
// Proxy MongoDB
`echo "=== PROXY MONGO ===" && ${PM2} logs backone-proxy --lines 5 --nostream 2>&1 | grep -E "(MongoDB|Scheduler|Connected|Capacity)" | tail -5`,
// Domain via HTTP (should 301 → HTTPS)
`echo "=== DOMAIN HTTP ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://demoplace.my.id/`,
// Domain via HTTPS from server itself
`echo "=== DOMAIN HTTPS ===" && curl -sk -o /dev/null -w "HTTP %{http_code}" https://demoplace.my.id/ 2>&1`,
]);
// External checks from local machine
console.log('\n\n══ External checks from local machine ══\n');
const tests = [
{ url: 'https://demoplace.my.id/', label: 'Root (expect 307 redirect to /login)' },
{ url: 'https://demoplace.my.id/login', label: 'Login page (expect 200)' },
{ url: 'https://demoplace.my.id/api/health', label: 'Backend health via domain (expect 200)' },
{ url: 'http://demoplace.my.id/', label: 'HTTP root (expect 301 HTTPS redirect)' },
];
for (const { url, label } of tests) {
const r = await fetch(url);
const ok = r.status >= 200 && r.status < 400;
const icon = ok ? '✅' : r.status === 0 ? '⚠️ ' : '❌';
console.log(` ${icon} [${String(r.status).padEnd(3)}] ${label}`);
if (r.headers?.location) console.log(` → Redirect: ${r.headers.location}`);
if (r.error) console.log(` → Error: ${r.error}`);
if (r.status === 200 && r.body) {
const preview = r.body.replace(/\s+/g, ' ').substring(0, 80);
console.log(` → Body: ${preview}`);
}
}
console.log('\n══════════════════════════════════════════════════════════════');
console.log(' Deployment Summary:');
console.log(' ┌─────────────────────────────────────────────────────┐');
console.log(' │ 🌐 Dashboard URL : https://demoplace.my.id │');
console.log(' │ 🔒 Backend : 127.0.0.1:3001 (internal only) │');
console.log(' │ 🔒 Proxy : 127.0.0.1:4000 (internal only) │');
console.log(' │ 🗄️ MongoDB : mongodb.prod.proit.id:27017 │');
console.log(' │ 🚀 Node.js : v18.19.1 │');
console.log(' │ ⚙️ PM2 : 3 processes (all online) │');
console.log(' └─────────────────────────────────────────────────────┘');
console.log('══════════════════════════════════════════════════════════\n');
}
main().catch(err => { console.error('[FATAL]', err); process.exit(1); });
+53
View File
@@ -0,0 +1,53 @@
// scripts/find-chunks.js
// Cari di mana file chunks yang benar berada di server
'use strict';
const { Client } = require('ssh2');
const conn = new Client();
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
function exec(conn, cmd) {
console.log('\n$ ' + cmd.substring(0, 80));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
conn.on('ready', async () => {
// 1. Cari file chunk yang direferensikan HTML
await exec(conn, 'find ' + PUB + ' -name "3km6z-n6wbgrs.js" 2>/dev/null | head -5');
await exec(conn, 'find ' + PUB + ' -name "21luguo5_g2uu.js" 2>/dev/null | head -5');
await exec(conn, 'find ' + PUB + ' -name "1u6dw_tm45utz.css" 2>/dev/null | head -5');
// 2. Cek isi static/static di standalone
const sa = PUB + '/.next/standalone/.next/static/static';
await exec(conn, 'ls ' + sa + '/ 2>/dev/null | head -10 || echo "Tidak ada nested static"');
// 3. Cek BUILD_ID utama
await exec(conn, 'cat ' + PUB + '/.next/BUILD_ID 2>/dev/null || echo "NO_BUILDID"');
// 4. Cek build-manifest.json untuk lihat chunk names
await exec(conn, 'cat ' + PUB + '/.next/standalone/.next/build-manifest.json 2>/dev/null | python3 -m json.tool 2>/dev/null | grep -A2 "login" | head -20');
// 5. Cek app-path-routes-manifest
await exec(conn, 'cat ' + PUB + '/.next/standalone/.next/app-path-routes-manifest.json 2>/dev/null | head -20');
// 6. Cek apakah ada next.js build di parent directory
await exec(conn, 'find /home/adminbackend -name "3km6z-n6wbgrs.js" 2>/dev/null | head -5');
// 7. Cek total disk structure
await exec(conn, 'du -sh ' + PUB + '/.next/standalone/.next/ 2>/dev/null');
await exec(conn, 'du -sh ' + PUB + '/.next/ 2>/dev/null | head -5');
conn.end();
console.log('\nDone.');
}).connect({
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 30000
});
+131
View File
@@ -0,0 +1,131 @@
// scripts/fix-and-restart.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Fix standalone structure & reload PM2 with new code
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const COMMANDS = [
// ── Step 1: Fix the standalone directory structure ──────────────────────────
// Next.js standalone server.js expects .next/static and public INSIDE the standalone dir
`echo "=== STEP 1: Create standalone .next dir ==="`,
`mkdir -p ${ROOT}/.next/standalone/.next/`,
// Copy static chunks into standalone/.next/static
`echo "=== STEP 2: Copy static assets into standalone ===" && cp -r ${ROOT}/.next/static ${ROOT}/.next/standalone/.next/static && echo "static copied OK"`,
// Copy media files
`ls ${ROOT}/.next/standalone/.next/static/ 2>/dev/null | wc -l | xargs -I{} echo "Files in standalone static: {}"`,
// public is already copied in deploy script, verify it
`ls ${ROOT}/.next/standalone/public/ 2>/dev/null | head -5 || echo "WARNING: public dir missing in standalone"`,
// ── Step 2: Verify the ecosystem.config.js was updated ────────────────────
`echo "=== STEP 3: Show ecosystem.config.js script path ===" && grep "script" ${ROOT}/ecosystem.config.js | head -5`,
// ── Step 3: Reload PM2 with new ecosystem config ──────────────────────────
// Use reload instead of restart to minimize downtime
`echo "=== STEP 4: Reload PM2 frontend with new code ===" && cd ${ROOT} && NODE_ENV=production ${PM2} reload ecosystem.config.js --only backone-frontend 2>&1`,
// Also reload backend to pick up new code
`echo "=== STEP 5: Reload PM2 backend ===" && cd ${ROOT} && NODE_ENV=production ${PM2} reload ecosystem.config.js --only backone-backend 2>&1`,
// Reload proxy as well (it was already restarted but ensure env is correct)
`echo "=== STEP 6: Reload PM2 proxy ===" && cd ${ROOT} && NODE_ENV=production ${PM2} reload ecosystem.config.js --only backone-proxy 2>&1`,
// ── Step 4: Save PM2 state for auto-restart on reboot ─────────────────────
`echo "=== STEP 7: Save PM2 state ===" && ${PM2} save && echo "PM2 state saved"`,
// ── Step 5: Wait for processes to stabilize ────────────────────────────────
`sleep 8`,
// ── Step 6: Verify all processes ──────────────────────────────────────────
`echo "=== STEP 8: PM2 Status ===" && ${PM2} list`,
// ── Step 7: Health checks ─────────────────────────────────────────────────
`echo "=== BACKEND HEALTH ===" && curl -s http://127.0.0.1:3001/api/health`,
`echo ""`,
`echo "=== FRONTEND HTTP STATUS ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/ && echo ""`,
`echo "=== FRONTEND LOGIN PAGE ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login && echo ""`,
// ── Step 8: Check port 3000 is now listening ──────────────────────────────
`echo "=== PORT 3000 STATUS ===" && ss -tlnp 2>/dev/null | grep 3000 || netstat -tlnp 2>/dev/null | grep 3000 || echo "Port 3000 check done"`,
// ── Step 9: Show frontend logs ────────────────────────────────────────────
`echo "=== FRONTEND PM2 LOG (last 20 lines) ===" && ${PM2} logs backone-frontend --lines 20 --nostream 2>&1 | tail -25`,
// ── Step 10: Show proxy MongoDB connection status ─────────────────────────
`echo "=== PROXY LOG (last 15 lines) ===" && ${PM2} logs backone-proxy --lines 15 --nostream 2>&1 | tail -20`,
];
function runSshCommands(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
console.log('[SSH] Connected!\n');
let index = 0;
function runNext() {
if (index >= commands.length) {
ssh.end();
return;
}
const cmd = commands[index++];
const preview = cmd.length > 150 ? cmd.substring(0, 150) + '...' : cmd;
console.log(`\n$ ${preview}`);
ssh.exec(cmd, (err, stream) => {
if (err) {
console.error(`[ERROR] ${err.message}`);
runNext();
return;
}
stream.on('data', (data) => process.stdout.write(data.toString()));
stream.stderr.on('data', (data) => {
const text = data.toString();
if (!text.includes('npm warn') && !text.includes('npm notice')) {
process.stdout.write(`[ERR] ${text}`);
}
});
stream.on('close', () => runNext());
});
}
runNext();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix Standalone & Reload PM2 ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
try {
await runSshCommands(COMMANDS);
console.log('\n✅ Fix & reload complete!\n');
} catch (err) {
console.error('\n[FATAL]', err.message);
process.exit(1);
}
}
main();
+120
View File
@@ -0,0 +1,120 @@
// scripts/fix-apache-ssl.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Fix Apache HTTPS 502 Bad Gateway
// The SSL VirtualHost needs mod_proxy configuration.
// cPanel uses Apache with .htaccess for proxy rules, but SSL VirtualHost
// may need specific directives to enable mod_proxy_http for the [P] flag.
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const COMMANDS = [
// Investigate Apache config for SSL
`echo "=== APACHE MODULES ===" && apache2ctl -M 2>/dev/null | grep -i proxy | head -10 || httpd -M 2>/dev/null | grep -i proxy | head -10 || echo "Cannot check Apache modules"`,
// Check what's actually at the SSL port - is it Apache or Nginx or something else?
`echo "=== SSL PORT OWNER ===" && ss -tlnp | grep 443`,
// Check SSL log for 502 cause
`echo "=== SSL ACCESS LOG (last 10 502s) ===" && tail -50 /home/adminbackend/logs/demoplace.my.id-ssl_log 2>/dev/null | grep "502" | tail -10 || echo "No SSL log or no 502s in log"`,
`echo "=== SSL ERROR LOG (last 15) ===" && tail -15 /home/adminbackend/logs/demoplace.my.id-ssl_log 2>/dev/null || echo "No SSL log found"`,
// Find all log files for this domain
`echo "=== DOMAIN LOG FILES ===" && ls /home/adminbackend/logs/*demoplace* 2>/dev/null || echo "No domain-specific logs"`,
// Check Apache vhost config (cPanel stores them here)
`echo "=== APACHE SSL VHOST ===" && cat /etc/apache2/conf.d/userdata/ssl/2_4/adminbackend/demoplace.my.id/custom_subdirectory.conf 2>/dev/null || ls /etc/apache2/conf.d/userdata/ssl/2_4/adminbackend/ 2>/dev/null || echo "Cannot read Apache vhost config"`,
// Check all .htaccess files in path
`echo "=== ROOT HTACCESS ===" && cat ${ROOT}/.htaccess`,
// Check if mod_proxy is loaded
`echo "=== MOD_PROXY CHECK ===" && test -f /etc/apache2/mods-enabled/proxy.load && echo "mod_proxy ENABLED" || echo "mod_proxy NOT enabled"`,
`test -f /etc/apache2/mods-enabled/proxy_http.load && echo "mod_proxy_http ENABLED" || echo "mod_proxy_http NOT enabled"`,
// The issue may be cPanel's "nobody" user restriction
// Try using ProxyPass in .htaccess with explicit ProxyPassReverse
`echo "=== UPDATING .HTACCESS ===" && cat > ${ROOT}/.htaccess << 'EOF'
Options -MultiViews
RewriteEngine On
# Force HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Proxy all requests to Next.js (port 3000)
RewriteRule ^(.*)$ http://127.0.0.1:3000/$1 [P,L,QSA]
ProxyPassReverse / http://127.0.0.1:3000/
EOF
echo ".htaccess updated"`,
// Read the new .htaccess to verify
`echo "=== NEW HTACCESS ===" && cat ${ROOT}/.htaccess`,
// Wait a moment for Apache to pick up changes
`sleep 2`,
// Test from server itself via HTTPS
`echo "=== HTTPS TEST FROM SERVER ===" && curl -sk -o /dev/null -w "HTTP %{http_code}" https://demoplace.my.id/ 2>&1`,
`echo "=== HTTPS LOGIN FROM SERVER ===" && curl -sk -o /dev/null -w "HTTP %{http_code}" https://demoplace.my.id/login 2>&1`,
// Check if it's a proxy timeout issue - try with verbose curl
`echo "=== VERBOSE HTTPS TEST ===" && curl -skv https://demoplace.my.id/ 2>&1 | grep -E "(<|>|\\*) " | head -20`,
// Alternative: check if cPanel has a NodeJS app manager entry interfering
`echo "=== CPANEL USERDATA ===" && ls /home/adminbackend/.cpanel/userdata/ 2>/dev/null`,
`cat /home/adminbackend/.cpanel/userdata/demoplace.my.id_SSL 2>/dev/null | head -30 || echo "No cPanel userdata for SSL"`,
`cat /home/adminbackend/.cpanel/userdata/demoplace.my.id 2>/dev/null | head -30 || echo "No cPanel userdata"`,
// Check Apache logs specifically for proxy errors
`echo "=== APACHE ERROR LOG ===" && tail -20 /var/log/apache2/error.log 2>/dev/null | grep -i "demoplace\|proxy\|502" | head -10 || echo "Cannot read Apache error log"`,
// PM2 processes still healthy?
`echo "=== PM2 STATUS ===" && ${PM2} list`,
`echo "=== FRONTEND HEALTH ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
];
function runSsh(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
let i = 0;
function next() {
if (i >= commands.length) { ssh.end(); return; }
const cmd = commands[i++];
console.log(`\n$ ${cmd.substring(0, 100)}${cmd.length > 100 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error('[ERR]', err.message); next(); return; }
stream.on('data', d => process.stdout.write(d.toString()));
stream.stderr.on('data', d => { const t = d.toString(); if (!t.includes('npm warn') && !t.includes('notice')) process.stdout.write(t); });
stream.on('close', next);
});
}
next();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix Apache HTTPS 502 Bad Gateway ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
await runSsh(COMMANDS);
console.log('\n✅ Apache SSL investigation complete!\n');
}
main().catch(err => { console.error('[FATAL]', err); process.exit(1); });
+141
View File
@@ -0,0 +1,141 @@
// scripts/fix-css-and-security.js
// 1. Fix CSS tidak termuat: buat symlink public_html/_next -> public_html/.next
// 2. Security audit: hapus cmd.php dan file berbahaya lainnya
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = {
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
function sshExec(conn, cmd, label = '') {
if (label) console.log(`\n[${label}]`);
console.log(`$ ${cmd.substring(0, 100)}${cmd.length > 100 ? '...' : ''}`);
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '';
stream.on('close', () => resolve(out))
.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); });
});
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix CSS + Security Audit ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
const conn = new Client();
await new Promise((resolve, reject) => { conn.on('ready', resolve).on('error', reject).connect(SSH); });
console.log('[SSH] Terhubung!\n');
// ── 1. SECURITY AUDIT ─────────────────────────────────────────────────
await sshExec(conn,
`echo "=== FILE BERBAHAYA ===" && ls -la ${PUB}/*.php ${PUB}/*.sh 2>/dev/null || echo "Tidak ada .php/.sh di root"`,
'1. Audit file PHP di public_html'
);
// Hapus cmd.php jika ada (file backdoor berbahaya)
await sshExec(conn,
`[ -f ${PUB}/cmd.php ] && rm -f ${PUB}/cmd.php && echo "DIHAPUS: cmd.php" || echo "cmd.php tidak ada (aman)"`,
'2. Hapus cmd.php (backdoor)'
);
// Hapus proxy.php lama (tidak diperlukan lagi karena nginx routing langsung ke port 3000)
await sshExec(conn,
`[ -f ${PUB}/proxy.php ] && rm -f ${PUB}/proxy.php && echo "DIHAPUS: proxy.php (tidak diperlukan)" || echo "proxy.php tidak ada"`,
'3. Hapus proxy.php (tidak dibutuhkan)'
);
// ── 2. CEK STRUKTUR DIREKTORI ──────────────────────────────────────────
await sshExec(conn,
`echo "=== STRUKTUR public_html ===" && ls -la ${PUB}/ | head -30`,
'4. Cek struktur public_html'
);
await sshExec(conn,
`echo "=== .next directory ===" && ls -la ${PUB}/.next/ 2>/dev/null | head -10 || echo "TIDAK ADA .next/"`,
'5. Cek .next directory'
);
await sshExec(conn,
`echo "=== static assets ===" && ls -la ${PUB}/.next/static/ 2>/dev/null | head -10 || echo "Tidak ada .next/static/"`,
'6. Cek .next/static'
);
await sshExec(conn,
`echo "=== _next symlink ===" && ls -la ${PUB}/_next 2>/dev/null || echo "Symlink _next belum ada"`,
'7. Cek symlink _next'
);
// ── 3. FIX CSS: Buat symlink _next → .next ────────────────────────────
await sshExec(conn,
`[ -e ${PUB}/_next ] && echo "Sudah ada _next" || (ln -s ${PUB}/.next ${PUB}/_next && echo "BERHASIL: Symlink _next → .next dibuat")`,
'8. Buat symlink _next -> .next'
);
// Verifikasi symlink
await sshExec(conn,
`ls -la ${PUB}/_next && ls ${PUB}/_next/static/ | head -5`,
'9. Verifikasi symlink'
);
// ── 4. PERBARUI .htaccess (bersih tanpa proxy.php) ────────────────────
const cleanHtaccess = `# BackOne DPI — Apache .htaccess
# Redirect HTTP ke HTTPS saja
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
`;
await sshExec(conn,
`printf '%s' ${JSON.stringify(cleanHtaccess)} > ${PUB}/.htaccess && echo "BERHASIL: .htaccess diperbarui"`,
'10. Perbarui .htaccess (hanya redirect HTTPS)'
);
// ── 5. TEST CSS LOADING ─────────────────────────────────────────────────
console.log('\n[11. Test loading CSS dari /_next/static/]');
await sshExec(conn,
`CSS_FILE=$(ls ${PUB}/.next/static/css/*.css 2>/dev/null | head -1) && ` +
`[ -n "$CSS_FILE" ] && ` +
`FILENAME=$(basename "$CSS_FILE") && ` +
`curl -sk -o /dev/null -w "CSS via domain: %{http_code} (size: %{size_download} bytes)" https://demoplace.my.id/_next/static/css/$FILENAME || ` +
`echo "Tidak ada file CSS ditemukan"`,
'11. Test CSS via domain'
);
conn.end();
// ── Test dari luar ──────────────────────────────────────────────────────
console.log('\n[12. Test domain setelah fix...]');
await new Promise(r => setTimeout(r, 2000));
for (const path of ['/login', '/api/health']) {
const r = await new Promise(resolve => {
const req = https.get(`https://demoplace.my.id${path}`,
{ timeout: 10000, rejectUnauthorized: false },
res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, body: body.substring(0, 100) }));
}
);
req.on('error', e => resolve({ status: 0, error: e.message }));
});
const icon = r.status === 200 ? '✅' : r.status === 301 ? '↩️ ' : '❌';
console.log(` ${icon} https://demoplace.my.id${path} → HTTP ${r.status}`);
if (path === '/api/health') console.log(` Body: ${r.body}`);
}
console.log('\n✅ Fix selesai! Coba refresh https://demoplace.my.id/login\n');
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+171
View File
@@ -0,0 +1,171 @@
// scripts/fix-env-loader.js
// Fix: Buat Node.js env loader yang aman (tanpa bash glob expansion)
'use strict';
const { Client } = require('ssh2');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 90));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
// Konten start-with-env.js yang akan ditulis ke server
const startWithEnvContent = `'use strict';
/**
* BackOne DPI - Safe Environment Loader for Next.js Standalone
* Reads .env.production safely without bash glob expansion issues.
*/
const path = require('path');
const fs = require('fs');
const envFile = path.join(__dirname, '.env.production');
if (fs.existsSync(envFile)) {
const lines = fs.readFileSync(envFile, 'utf8').split(/\\r?\\n/);
let loaded = 0;
for (const raw of lines) {
const line = raw.trim();
// Skip comments and empty lines
if (!line || line.startsWith('#')) continue;
const eqIdx = line.indexOf('=');
if (eqIdx === -1) continue;
const key = line.substring(0, eqIdx).trim();
// Only accept valid environment variable names
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
const val = line.substring(eqIdx + 1); // Do NOT trim value - preserve exact content
if (!(key in process.env)) {
process.env[key] = val;
loaded++;
}
}
console.log('[startup] Loaded ' + loaded + ' environment variables from .env.production');
console.log('[startup] NETIFY_API_KEY set:', !!process.env.NETIFY_API_KEY);
console.log('[startup] MONGODB_URI set: ', !!process.env.MONGODB_URI);
} else {
console.warn('[startup] WARNING: .env.production not found at:', envFile);
}
// Start Next.js standalone server
require('./.next/standalone/server.js');
`;
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
// 1. Upload start-with-env.js ke server
await exec(conn,
'cat > ' + PUB + '/start-with-env.js << \'NODELOADER_EOF\'\n' + startWithEnvContent + '\nNODELOADER_EOF\n' +
'echo "Node.js loader dibuat"',
'1. Buat start-with-env.js'
);
await exec(conn, 'wc -l ' + PUB + '/start-with-env.js', '2. Verifikasi file');
// 2. Update ecosystem.config.js untuk gunakan Node.js loader (bukan bash wrapper)
const ecoContent = `// ecosystem.config.js — PM2 Config (server-side)
module.exports = {
apps: [
{
name: 'backone-proxy',
script: './proxy/index.js',
cwd: '${PUB}',
instances: 1,
exec_mode: 'fork',
watch: false,
node_args: '--max-old-space-size=256',
max_memory_restart: '300M',
restart_delay: 5000,
max_restarts: 10,
env_file: '.env.production',
env: { NODE_ENV: 'production' },
error_file: './logs/proxy-error.log',
out_file: './logs/proxy-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
},
{
name: 'backone-backend',
script: './backend/server.js',
cwd: '${PUB}',
instances: 1,
exec_mode: 'fork',
watch: false,
node_args: '--max-old-space-size=256',
max_memory_restart: '400M',
restart_delay: 3000,
max_restarts: 10,
env_file: '.env.production',
env: { NODE_ENV: 'production' },
error_file: './logs/backend-error.log',
out_file: './logs/backend-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
},
{
name: 'backone-frontend',
script: './start-with-env.js',
cwd: '${PUB}',
instances: 1,
exec_mode: 'fork',
watch: false,
node_args: '--max-old-space-size=512',
max_memory_restart: '700M',
restart_delay: 3000,
max_restarts: 10,
env: {
NODE_ENV: 'production',
PORT: '3000',
HOSTNAME: '127.0.0.1',
NEXT_TELEMETRY_DISABLED: '1',
},
error_file: './logs/frontend-error.log',
out_file: './logs/frontend-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
},
],
};`;
await exec(conn,
'cat > ' + PUB + '/ecosystem.config.js << \'ECO_EOF\'\n' + ecoContent + '\nECO_EOF\necho "ecosystem.config.js diperbarui"',
'3. Update ecosystem.config.js'
);
// 3. Delete & restart frontend
await exec(conn,
'cd ' + PUB + ' && ' + PM2 + ' delete backone-frontend 2>/dev/null; ' +
PM2 + ' start ecosystem.config.js --only backone-frontend && echo "START OK"',
'4. Restart frontend dengan Node.js loader'
);
// 4. Tunggu startup
await new Promise(r => setTimeout(r, 8000));
// 5. Cek out log (harus tampil "Loaded X environment variables")
await exec(conn, 'tail -10 ' + PUB + '/logs/frontend-out.log 2>/dev/null', '5. Out log (env loader output)');
await exec(conn, 'tail -5 ' + PUB + '/logs/frontend-error.log 2>/dev/null', '6. Error log terbaru');
// 6. Test agents
await exec(conn, 'curl -sk -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/agents', '7. Test /agents page');
// 7. PM2 status
await exec(conn, PM2 + ' list 2>/dev/null | grep backone', '8. PM2 status');
conn.end();
console.log('\n✅ Selesai!\n');
}
main().catch(e => console.error('[FATAL]', e.message));
+78
View File
@@ -0,0 +1,78 @@
// scripts/fix-env-vars.js
// Fix: Pastikan environment variables dari .env.production dimuat oleh PM2 frontend
'use strict';
const { Client } = require('ssh2');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 90));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
// 1. Cek env vars yang diterima proses PM2 sekarang
await exec(conn, PM2 + ' env 3 2>/dev/null | grep -E "(NETIFY|BACKONE|MONGO|JWT)" | head -15', '1. Env vars di PM2 proses 3 (frontend)');
// 2. Cek isi .env.production
await exec(conn, 'cat ' + PUB + '/.env.production 2>/dev/null | grep -v "#" | grep -v "^$" | head -30', '2. Isi .env.production (non-comment)');
// 3. Export vars dari .env.production dan restart dengan env vars eksplisit
// Baca .env.production, parse, dan set ke PM2
const envScript = `
set -a
source ${PUB}/.env.production 2>/dev/null
set +a
export NETIFY_API_KEY="$NETIFY_API_KEY"
export NETIFY_ORG_UUID="$NETIFY_ORG_UUID"
export NETIFY_SITE_UUIDS="$NETIFY_SITE_UUIDS"
export NETIFY_INFORMATICS_BASE_URL="$NETIFY_INFORMATICS_BASE_URL"
export MONGODB_URI="$MONGODB_URI"
export JWT_SECRET="$JWT_SECRET"
export NEXT_PUBLIC_API_URL="$NEXT_PUBLIC_API_URL"
echo "API_KEY=$NETIFY_API_KEY"
echo "MONGO_URI_PARTIAL=$(echo $MONGODB_URI | cut -c1-30)..."
`.trim();
await exec(conn, 'bash -c \'' + envScript.replace(/\n/g, '; ') + '\'', '3. Verifikasi env vars bisa dibaca dari .env.production');
// 4. Delete dan restart PM2 proses dengan ecosystem file yang membaca env_file
await exec(conn,
'cd ' + PUB + ' && ' + PM2 + ' delete backone-frontend 2>/dev/null; ' +
PM2 + ' start ecosystem.config.js --only backone-frontend --env production && echo "START OK"',
'4. Restart frontend via ecosystem.config.js'
);
// 5. Verifikasi env vars sekarang dimuat
await new Promise(r => setTimeout(r, 5000));
await exec(conn, PM2 + ' env 3 2>/dev/null | grep -E "(NETIFY|BACKONE|MONGO)" | head -10', '5. Verifikasi env vars setelah restart');
// 6. Test agents page
await new Promise(r => setTimeout(r, 5000));
await exec(conn, 'curl -sk -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/agents', '6. Test /agents');
// 7. Cek error log terbaru
await exec(conn, 'tail -5 ' + PUB + '/logs/frontend-error.log 2>/dev/null', '7. Error log terbaru');
// 8. PM2 status akhir
await exec(conn, PM2 + ' list 2>/dev/null | grep backone', '8. PM2 status akhir');
conn.end();
console.log('\n✅ Selesai!\n');
}
main().catch(e => console.error('[FATAL]', e.message));
+110
View File
@@ -0,0 +1,110 @@
// scripts/fix-https.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Fix HTTPS 502 Bad Gateway Issue
// The cPanel Apache proxy is getting 502 — need to investigate and fix
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const COMMANDS = [
// Check current frontend status
`echo "=== FRONTEND PROCESS ==="`,
`ps aux | grep node | grep -v grep`,
`ss -tlnp | grep 3000`,
// Check if frontend is responding internally
`echo "=== INTERNAL FRONTEND TEST ===" && curl -sv http://127.0.0.1:3000/ 2>&1 | grep -E "(< HTTP|< Location|Connected|curl)" | head -10`,
// Check the .htaccess to confirm proxy setup
`echo "=== .HTACCESS CONTENT ===" && cat ${ROOT}/.htaccess`,
// Check Apache error log for this domain
`echo "=== APACHE ERROR (last 20 lines) ===" && tail -20 /home/adminbackend/logs/demoplace.my.id-ssl_log 2>/dev/null || tail -20 /home/adminbackend/logs/demoplace.my.id-error_log 2>/dev/null || tail -20 /home/adminbackend/logs/error_log 2>/dev/null || echo "No Apache error log found"`,
// Check all logs
`echo "=== LOG FILES ===" && ls /home/adminbackend/logs/ 2>/dev/null | head -20`,
// Check domain SSL logs
`echo "=== SSL LOG ===" && tail -30 /home/adminbackend/logs/demoplace.my.id-ssl_log 2>/dev/null | grep -E "(502|500|error|proxy)" | tail -15 || echo "No SSL log"`,
// Try the domain via HTTP (non-SSL)
`echo "=== HTTP (non-SSL) TEST ===" && curl -sv http://demoplace.my.id/ 2>&1 | grep -E "(< HTTP|Location|Connected)" | head -5`,
// Check PM2 frontend is running
`echo "=== PM2 FRONTEND STATUS ===" && ${PM2} show backone-frontend 2>&1 | grep -E "(status|pid|uptime|restart)" | head -10`,
// Check frontend log for errors
`echo "=== FRONTEND LOG (errors) ===" && ${PM2} logs backone-frontend --lines 30 --nostream 2>&1 | grep -E "(Error|error|ECONNRESET|Ready|✓)" | tail -15`,
// Re-check port
`echo "=== PORT 3000 DETAILED ===" && ss -tlnp | grep 3000`,
// Try direct curl to frontend with more verbose output
`echo "=== DETAILED FRONTEND CURL ===" && curl -v http://127.0.0.1:3000/login 2>&1 | head -30`,
// Check if there's an issue with the standalone server path in PM2
`echo "=== PM2 SCRIPT PATH ===" && ${PM2} show backone-frontend 2>&1 | grep -E "(script|cwd|exec)" | head -5`,
// Check node version
`echo "=== NODE VERSION ===" && node --version`,
// Check if frontend started correctly after restart
`echo "=== FRONTEND READY STATUS ===" && ${PM2} logs backone-frontend --lines 10 --nostream 2>&1 | tail -15`,
// Try restarting frontend
`echo "=== RESTARTING FRONTEND ===" && ${PM2} restart backone-frontend && sleep 5 && echo "Restarted"`,
// Final port check
`echo "=== FINAL PORT CHECK ===" && ss -tlnp | grep 3000`,
`echo "=== FINAL CURL ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
// Check if maybe cPanel node app is conflicting
`echo "=== CPANEL NODE APPS ===" && ls /home/adminbackend/nodevenv/ 2>/dev/null || echo "No cPanel Node.js virtual environments"`,
`cat /home/adminbackend/.cpanel/nvdata/node_application_config 2>/dev/null | head -30 || echo "No cPanel Node config"`,
];
function runSsh(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
console.log('[SSH] Connected!\n');
let i = 0;
function next() {
if (i >= commands.length) { ssh.end(); return; }
const cmd = commands[i++];
console.log(`\n$ ${cmd.substring(0, 130)}${cmd.length > 130 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error('[ERR]', err.message); next(); return; }
stream.on('data', d => process.stdout.write(d.toString()));
stream.stderr.on('data', d => { const t = d.toString(); if (!t.includes('npm warn') && !t.includes('notice')) process.stdout.write(t); });
stream.on('close', next);
});
}
next();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — HTTPS 502 Investigation & Fix ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
await runSsh(COMMANDS);
console.log('\n✅ Investigation complete!\n');
}
main().catch(err => { console.error('[FATAL]', err); process.exit(1); });
+88
View File
@@ -0,0 +1,88 @@
// scripts/fix-missing-css.js
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
const SA = PUB + '/.next/standalone/.next/static';
const BP = '/home/adminbackend/backone-production/.next/static';
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 80));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function httpGet(url) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 12000, rejectUnauthorized: false }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, size: body.length, type: res.headers['content-type'] }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
// 1. Cari file CSS yang hilang
await exec(conn, 'find /home/adminbackend -name "1oskchnhjm4n8.css" 2>/dev/null | head -5', '1. Cari CSS hilang');
// 2. List CSS di backone-production
await exec(conn, 'find ' + BP + ' -name "*.css" 2>/dev/null | head -10', '2. CSS di backone-production');
// 3. Full rsync chunks (paksa semua file)
await exec(conn,
'rsync -av ' + BP + '/chunks/ ' + SA + '/chunks/ 2>&1 | grep "css\\|sent\\|total" | head -10',
'3. Full rsync chunks'
);
// 4. Verifikasi CSS setelah rsync
await exec(conn, 'find ' + SA + '/chunks -name "*.css" 2>/dev/null', '4. CSS di standalone setelah rsync');
// 5. Cek total file
await exec(conn, 'ls ' + SA + '/chunks/ | wc -l', '5. Total file chunks');
conn.end();
// 6. Final test
console.log('\n[6. Final test static files...]');
await new Promise(r => setTimeout(r, 2000));
const files = [
'/_next/static/chunks/3km6z-n6wbgrs.js',
'/_next/static/chunks/1u6dw_tm45utz.css',
'/_next/static/chunks/1oskchnhjm4n8.css',
'/_next/static/chunks/21luguo5_g2uu.js',
'/_next/static/chunks/1-8s9_t85wwr4.js',
'/_next/static/chunks/2n6j37owj-kaf.js',
];
let allOk = true;
for (const f of files) {
const r = await httpGet('https://demoplace.my.id' + f);
const icon = r.status === 200 ? '✅' : '❌';
const ext = f.endsWith('.css') ? 'CSS' : 'JS ';
console.log(' ' + icon + ' [' + ext + '] HTTP ' + r.status + ' | ' + (r.size || 0) + ' bytes');
if (r.status !== 200) { console.log(' → ' + f); allOk = false; }
}
if (allOk) {
console.log('\n🎉 SEMUA CSS/JS BERHASIL! Lakukan hard refresh (Ctrl+Shift+R)\n');
} else {
console.log('\n⚠ Masih ada yang 404. Cek manual.\n');
}
}
main().catch(e => console.error('[FATAL]', e.message));
+80
View File
@@ -0,0 +1,80 @@
// scripts/fix-mongoose-module.js
// Fix: Copy mongoose ke standalone/node_modules agar tersedia sebagai external package
'use strict';
const { Client } = require('ssh2');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
const SA = PUB + '/.next/standalone';
const BP = '/home/adminbackend/backone-production';
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 90));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
// 1. Cek lokasi mongoose
await exec(conn, 'ls ' + SA + '/node_modules/mongoose 2>/dev/null | head -3 || echo "Tidak ada di standalone/node_modules"', '1. Cek mongoose di standalone');
await exec(conn, 'ls ' + BP + '/node_modules/mongoose 2>/dev/null | head -3 || echo "Tidak ada di backone-production"', '2. Cek mongoose di backone-production');
await exec(conn, 'ls ' + PUB + '/node_modules/mongoose 2>/dev/null | head -3 || echo "Tidak ada di public_html/node_modules"', '3. Cek mongoose di public_html');
// 2. Copy mongoose ke standalone/node_modules (dari backone-production)
const mongooseSrc = BP + '/node_modules/mongoose';
const mongooseDst = SA + '/node_modules/mongoose';
await exec(conn,
'[ -d ' + mongooseSrc + ' ] && (' +
' [ -e ' + mongooseDst + ' ] && echo "Sudah ada" || ' +
' (cp -r ' + mongooseSrc + ' ' + mongooseDst + ' && echo "BERHASIL: mongoose disalin ke standalone") ' +
') || echo "Sumber tidak ditemukan"',
'4. Copy mongoose ke standalone/node_modules'
);
// 3. Verifikasi
await exec(conn, 'ls ' + mongooseDst + ' 2>/dev/null | head -5 || echo "GAGAL: tidak ada"', '5. Verifikasi mongoose di standalone');
// 4. Juga pastikan dependensi mongoose tersedia (bson, etc.)
const deps = ['bson', 'kareem', 'mquery', 'mpath', 'sift'];
for (const dep of deps) {
const src = BP + '/node_modules/' + dep;
const dst = SA + '/node_modules/' + dep;
await exec(conn,
'[ -d ' + src + ' ] && ([ -e ' + dst + ' ] || (cp -r ' + src + ' ' + dst + ' && echo "Copied: ' + dep + '")) || echo "Skip: ' + dep + '"',
null
);
}
// 5. Restart frontend
await exec(conn, PM2 + ' restart backone-frontend && echo "RESTART OK"', '6. Restart backone-frontend');
// 6. Tunggu dan cek status
await new Promise(r => setTimeout(r, 5000));
await exec(conn, PM2 + ' list 2>/dev/null | grep backone', '7. Status PM2 setelah restart');
// 7. Test halaman agents
await new Promise(r => setTimeout(r, 3000));
await exec(conn, 'curl -sk -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/agents', '8. Test /agents setelah fix');
// 8. Cek error log setelah restart
await exec(conn, 'tail -10 ' + PUB + '/logs/frontend-error.log 2>/dev/null', '9. Error log terbaru');
conn.end();
console.log('\n✅ Selesai! Refresh browser ke https://demoplace.my.id/agents\n');
}
main().catch(e => console.error('[FATAL]', e.message));
+156
View File
@@ -0,0 +1,156 @@
// scripts/fix-pm2-env-final.js
// Fix definitif: Buat startup wrapper script yang load .env.production sebelum menjalankan Next.js
'use strict';
const { Client } = require('ssh2');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 90));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
// 1. Buat wrapper script yang source .env.production
const wrapperContent = [
'#!/bin/bash',
'# BackOne DPI - Frontend startup wrapper',
'# Source environment variables from .env.production',
'ENV_FILE="' + PUB + '/.env.production"',
'if [ -f "$ENV_FILE" ]; then',
' set -a',
' source "$ENV_FILE"',
' set +a',
' echo "[startup] Environment variables loaded from .env.production"',
'else',
' echo "[startup] WARNING: .env.production not found at $ENV_FILE"',
'fi',
'# Start Next.js standalone server',
'exec node ' + PUB + '/.next/standalone/server.js',
].join('\n');
await exec(conn,
'cat > ' + PUB + '/start-frontend.sh << \'WRAPPER_EOF\'\n' + wrapperContent + '\nWRAPPER_EOF\n' +
'chmod +x ' + PUB + '/start-frontend.sh && echo "Wrapper script dibuat"',
'1. Buat wrapper startup script'
);
await exec(conn, 'cat ' + PUB + '/start-frontend.sh', '2. Verifikasi isi wrapper script');
// 2. Update ecosystem.config.js di SERVER untuk gunakan wrapper script
const ecosystemContent = `// ecosystem.config.js — PM2 Production Config (Server-side, DO NOT COMMIT)
module.exports = {
apps: [
{
name: 'backone-proxy',
script: './proxy/index.js',
cwd: '${PUB}',
instances: 1,
exec_mode: 'fork',
watch: false,
node_args: '--max-old-space-size=256',
max_memory_restart: '300M',
restart_delay: 5000,
max_restarts: 10,
env_file: '.env.production',
env: { NODE_ENV: 'production' },
error_file: './logs/proxy-error.log',
out_file: './logs/proxy-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
},
{
name: 'backone-backend',
script: './backend/server.js',
cwd: '${PUB}',
instances: 1,
exec_mode: 'fork',
watch: false,
node_args: '--max-old-space-size=256',
max_memory_restart: '400M',
restart_delay: 3000,
max_restarts: 10,
env_file: '.env.production',
env: { NODE_ENV: 'production' },
error_file: './logs/backend-error.log',
out_file: './logs/backend-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
},
{
name: 'backone-frontend',
script: './start-frontend.sh',
interpreter: 'bash',
cwd: '${PUB}',
instances: 1,
exec_mode: 'fork',
watch: false,
max_memory_restart: '700M',
restart_delay: 3000,
max_restarts: 10,
env: {
NODE_ENV: 'production',
PORT: 3000,
HOSTNAME: '127.0.0.1',
NEXT_TELEMETRY_DISABLED: '1',
},
error_file: './logs/frontend-error.log',
out_file: './logs/frontend-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
},
],
};`;
await exec(conn,
'cat > ' + PUB + '/ecosystem.config.js << \'ECOSYSTEM_EOF\'\n' + ecosystemContent + '\nECOSYSTEM_EOF\n' +
'echo "ecosystem.config.js diperbarui"',
'3. Update ecosystem.config.js di server'
);
// 3. Delete & restart frontend dengan config baru
await exec(conn,
'cd ' + PUB + ' && ' + PM2 + ' delete backone-frontend 2>/dev/null; ' +
PM2 + ' start ecosystem.config.js --only backone-frontend && echo "START OK"',
'4. Start frontend dengan wrapper script'
);
// 4. Tunggu startup
await new Promise(r => setTimeout(r, 8000));
// 5. Verifikasi env vars
const listOut = await exec(conn, PM2 + ' list 2>/dev/null | grep backone-frontend', '5. PM2 status');
// Ambil ID proses baru
const match = listOut.match(/│\s+(\d+)\s+│\s+backone-frontend/);
if (match) {
const newId = match[1];
await exec(conn, PM2 + ' env ' + newId + ' 2>/dev/null | grep -E "(NETIFY|MONGO|JWT)" | head -5', '6. Env vars di proses baru');
}
// 6. Test agents
await exec(conn, 'curl -sk -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/agents', '7. Test /agents');
// 7. Error log terbaru
await exec(conn, 'tail -5 ' + PUB + '/logs/frontend-error.log 2>/dev/null', '8. Error log terbaru');
await exec(conn, 'tail -3 ' + PUB + '/logs/frontend-out.log 2>/dev/null', '9. Out log terbaru');
conn.end();
console.log('\n✅ Selesai!\n');
}
main().catch(e => console.error('[FATAL]', e.message));
+223
View File
@@ -0,0 +1,223 @@
// scripts/fix-proxy-headers.js
// Fix: proxy.php harus hapus Content-Length agar CSS/JS tidak terpotong
'use strict';
const SftpClient = require('ssh2-sftp-client');
const { Client } = require('ssh2');
const https = require('https');
const CONFIG = {
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
// ── proxy.php yang sudah diperbaiki ──────────────────────────────────────
// Fix utama: tambahkan content-length ke daftar header yang dibuang
// agar tidak terjadi mismatch antara ukuran compressed vs uncompressed
const PROXY_PHP = `<?php
/**
* BackOne DPI - PHP Reverse Proxy (Fixed)
* Forward semua request ke Next.js port 3000
* Fix: Content-Length dihapus agar tidak terjadi data truncation
*/
define('NEXTJS_URL', 'http://127.0.0.1:3000');
$uri = $_SERVER['REQUEST_URI'] ?? '/';
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$target = NEXTJS_URL . $uri;
// Headers yang diteruskan ke Next.js
$fwdHeaders = [
'X-Forwarded-For: ' . ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'),
'X-Forwarded-Proto: https',
'X-Real-IP: ' . ($_SERVER['REMOTE_ADDR'] ?? ''),
'Host: demoplace.my.id',
'Accept-Encoding: identity',
];
$allHeaders = getallheaders() ?: [];
$skip = ['host', 'connection', 'transfer-encoding', 'keep-alive', 'content-length', 'accept-encoding'];
foreach ($allHeaders as $name => $value) {
if (!in_array(strtolower($name), $skip)) {
$fwdHeaders[] = "$name: $value";
}
}
$body = in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE']) ? file_get_contents('php://input') : null;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $target,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $fwdHeaders,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 5,
// PENTING: Tidak pakai CURLOPT_ENCODING agar response tidak dikompresi
// (kita sudah kirim Accept-Encoding: identity ke Next.js)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false) {
http_response_code(502);
header('Content-Type: text/plain');
echo 'BackOne: Gagal terhubung ke Next.js. Error: ' . $curlError;
exit;
}
$responseHeaders = substr($response, 0, $headerSize);
$responseBody = substr($response, $headerSize);
http_response_code($httpCode ?: 200);
// Header yang TIDAK diteruskan ke browser
$skipRes = [
'transfer-encoding',
'connection',
'keep-alive',
'content-encoding',
'content-length', // FIX: hapus agar tidak terjadi size mismatch
];
foreach (explode("\\r\\n", $responseHeaders) as $header) {
$header = trim($header);
if (empty($header) || preg_match('/^HTTP\\//i', $header)) continue;
$colonPos = strpos($header, ':');
if ($colonPos === false) continue;
$headerName = strtolower(trim(substr($header, 0, $colonPos)));
if (in_array($headerName, $skipRes)) continue;
header($header, false);
}
echo $responseBody;
`;
// ── .htaccess dengan FollowSymLinks ──────────────────────────────────────
const HTACCESS = `# BackOne DPI — Apache .htaccess
Options +FollowSymLinks
# Redirect HTTP ke HTTPS
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Static Next.js files: sajikan langsung dari disk (via symlink _next -> .next)
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
# Semua request lain: teruskan ke PHP proxy (Next.js port 3000)
RewriteRule ^(.*)$ /proxy.php [L,QSA]
`;
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix CSS/JS Content-Length & Symlinks ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// ── Diagnosa dulu: cek apa yang dikirim ke browser untuk CSS ──────────
console.log('▶ Diagnosa: Cek CSS URL yang ada di HTML login page...');
const conn1 = new Client();
await new Promise((r, j) => { conn1.on('ready', r).on('error', j).connect(CONFIG); });
// Ambil HTML dari port 3000 dan cari CSS links
const htmlResult = await new Promise(resolve => {
conn1.exec(
`curl -sk http://127.0.0.1:3000/login | grep -o 'href="/_next/static/css/[^"]*"' | head -3`,
(err, s) => {
if (err) { resolve('error'); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; })
.on('close', () => resolve(out.trim()));
}
);
});
console.log(`CSS refs: ${htmlResult}`);
// Test apakah CSS bisa diakses langsung
if (htmlResult) {
const cssPath = htmlResult.match(/href="([^"]+)"/)?.[1];
if (cssPath) {
await new Promise(resolve => {
conn1.exec(
`curl -sk -D - http://127.0.0.1:3000${cssPath} | head -15`,
(err, s) => {
if (err) { resolve(); return; }
s.on('data', d => process.stdout.write(d.toString()))
.stderr.on('data', () => {})
.on('close', resolve);
}
);
});
}
}
conn1.end();
// ── Upload proxy.php dan .htaccess yang sudah diperbaiki ──────────────
console.log('\n▶ Upload perbaikan via SFTP...');
const sftp = new SftpClient();
await sftp.connect(CONFIG);
await sftp.put(Buffer.from(PROXY_PHP, 'utf8'), `${ROOT}/proxy.php`);
console.log(' ✅ proxy.php (fixed: Content-Length stripped, Accept-Encoding: identity)');
await sftp.put(Buffer.from(HTACCESS, 'utf8'), `${ROOT}/.htaccess`);
console.log(' ✅ .htaccess (fixed: +FollowSymLinks added)');
await sftp.end();
// ── Verifikasi CSS via domain ─────────────────────────────────────────
console.log('\n▶ Test CSS loading via domain (tunggu 3 detik)...');
await new Promise(r => setTimeout(r, 3000));
const conn2 = new Client();
await new Promise((r, j) => { conn2.on('ready', r).on('error', j).connect(CONFIG); });
// Ambil CSS URL dari domain lalu test
const cssUrlResult = await new Promise(resolve => {
conn2.exec(
`CSS=$(curl -sk https://demoplace.my.id/login | grep -o '/_next/static/css/[^"]*' | head -1); ` +
`[ -n "$CSS" ] && curl -sk -D - "https://demoplace.my.id$CSS" | head -10 || echo "Tidak ada CSS URL ditemukan"`,
(err, s) => {
if (err) { resolve(); return; }
s.on('data', d => process.stdout.write(d.toString()))
.stderr.on('data', () => {})
.on('close', resolve);
}
);
});
conn2.end();
// ── Final test ────────────────────────────────────────────────────────
console.log('\n▶ Final test domain...');
for (const path of ['/', '/login']) {
const r = await new Promise(resolve => {
const req = https.get(`https://demoplace.my.id${path}`,
{ timeout: 12000, rejectUnauthorized: false },
res => resolve({ status: res.statusCode, location: res.headers.location })
);
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
const icon = r.status === 200 ? '✅' : r.status >= 300 && r.status < 400 ? '↩️ ' : '❌';
console.log(` ${icon} https://demoplace.my.id${path} → HTTP ${r.status}`);
}
console.log('\n✅ Selesai! Refresh browser dengan Ctrl+Shift+R (hard refresh)\n');
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+129
View File
@@ -0,0 +1,129 @@
// scripts/fix-static-paths.js
// Menyalin static files dari backone-production ke standalone yang benar
// Masalah: Upload Windows membuat direktori dengan backslash literal di Linux
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = {
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 90));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function httpGet(url) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 12000, rejectUnauthorized: false }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, size: body.length, type: res.headers['content-type'] }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix Static Paths (Windows Backslash) ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Terhubung!\n');
const BP = '/home/adminbackend/backone-production';
const SA = '/home/adminbackend/web/demoplace.my.id/public_html/.next/standalone';
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
// 1. Cek BUILD_ID di backone-production
await exec(conn, 'cat ' + BP + '/.next/BUILD_ID 2>/dev/null || echo "NO_BUILDID"', '1. BUILD_ID backone-production');
// 2. Verifikasi chunk files ada di backone-production
await exec(conn,
'ls ' + BP + '/.next/static/chunks/ | grep -E "(3km6z|21luguo|1u6dw|1osk)" | head -10',
'2. Cek chunk files di backone-production'
);
// 3. Sinkronisasi static files dari backone-production ke standalone
await exec(conn,
'rsync -av --ignore-existing ' + BP + '/.next/static/ ' + SA + '/.next/static/ 2>&1 | tail -5',
'3. Rsync static files ke standalone/.next/static'
);
// 4. Verifikasi chunks sudah ada di standalone
await exec(conn,
'ls ' + SA + '/.next/static/chunks/ | grep -E "(3km6z|21luguo|1u6dw|1osk)" | head -10',
'4. Verifikasi chunks di standalone setelah rsync'
);
await exec(conn,
'ls ' + SA + '/.next/static/chunks/ | wc -l',
'5. Total file chunks di standalone'
);
// 5. Update symlink _next → standalone/.next
await exec(conn,
'rm -f ' + PUB + '/_next && ln -s ' + SA + '/.next ' + PUB + '/_next && echo "Symlink updated"',
'6. Update symlink _next → standalone/.next'
);
// 6. Verifikasi symlink
await exec(conn,
'ls -la ' + PUB + '/_next && ls ' + PUB + '/_next/static/chunks/ | grep -E "(3km6z|21luguo)" | head -5',
'7. Verifikasi symlink baru'
);
// 7. Juga sinkronisasi public/ dari backone-production jika dibutuhkan
await exec(conn,
'rsync -av --ignore-existing ' + BP + '/public/ ' + PUB + '/public/ 2>&1 | tail -3 || echo "Tidak perlu sync public/"',
'8. Sync public/ dari backone-production'
);
conn.end();
// 8. Test CSS/JS via domain
console.log('\n[9. Test CSS/JS via domain setelah fix...]');
await new Promise(r => setTimeout(r, 3000));
const files = [
'/_next/static/chunks/3km6z-n6wbgrs.js',
'/_next/static/chunks/1u6dw_tm45utz.css',
'/_next/static/chunks/1oskchnhjm4n8.css',
'/_next/static/chunks/21luguo5_g2uu.js',
'/_next/static/chunks/1-8s9_t85wwr4.js',
];
let allOk = true;
for (const f of files) {
const r = await httpGet('https://demoplace.my.id' + f);
const icon = r.status === 200 ? '✅' : '❌';
const ext = f.endsWith('.css') ? 'CSS' : 'JS ';
console.log(' ' + icon + ' [' + ext + '] HTTP ' + r.status + ' | ' + (r.size || 0) + ' bytes | ' + (r.type || 'N/A'));
if (r.status !== 200) { console.log(' → ' + f); allOk = false; }
}
if (allOk) {
console.log('\n🎉 SEMUA CSS/JS BERHASIL TERMUAT!');
console.log(' Lakukan HARD REFRESH (Ctrl+Shift+R) di browser.\n');
} else {
console.log('\n⚠️ Beberapa file masih 404. Mungkin perlu investigasi lebih lanjut.\n');
}
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+118
View File
@@ -0,0 +1,118 @@
// scripts/fix-symlink.js
// Fix: Perbarui symlink _next agar menunjuk ke .next/standalone/.next
// Ini adalah penyebab utama CSS/JS tidak termuat
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = {
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
function sshExec(conn, cmd, label = '') {
if (label) console.log(`\n[${label}]`);
console.log('$ ' + cmd.substring(0, 90) + (cmd.length > 90 ? '...' : ''));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function httpGet(url) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 10000, rejectUnauthorized: false }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, type: res.headers['content-type'], size: body.length }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix Symlink _next → standalone/.next ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Terhubung!\n');
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
const SA = `${PUB}/.next/standalone`;
// 1. Verifikasi struktur standalone
await sshExec(conn,
`ls ${SA}/.next/static/chunks/ | grep -E "(3km6z|21luguo|1u6dw|1osk)" | head -10`,
'1. Cek chunk files di standalone/.next'
);
await sshExec(conn,
`ls ${SA}/.next/static/chunks/*.css 2>/dev/null | head -5 || echo "Cek dengan wc"`,
'2. Cek CSS files di standalone'
);
await sshExec(conn,
`ls ${SA}/.next/static/chunks/ | wc -l && echo "file di chunks"`,
'3. Jumlah total file di chunks'
);
// 2. Update symlink
await sshExec(conn,
`rm -f ${PUB}/_next && ln -s ${SA}/.next ${PUB}/_next && echo "BERHASIL: symlink diperbaiki"`,
'4. Update symlink _next → standalone/.next'
);
// 3. Verifikasi symlink baru
await sshExec(conn,
`ls -la ${PUB}/_next && ls ${PUB}/_next/static/chunks/ | head -5`,
'5. Verifikasi symlink baru'
);
// 4. Juga update public/ symlink jika belum ada
await sshExec(conn,
`ls -la ${PUB}/public 2>/dev/null || echo "public/ tidak ada sebagai symlink"`,
'6. Cek public/ directory'
);
// Buat symlink public → standalone/public jika ada
await sshExec(conn,
`[ -d ${SA}/public ] && ([ -e ${PUB}/public_sa ] || ln -s ${SA}/public ${PUB}/public_sa) && echo "standalone/public tersedia" || echo "standalone/public tidak ada"`,
'7. Cek standalone/public'
);
conn.end();
// 5. Test CSS/JS loading setelah fix
console.log('\n[8. Test CSS/JS via domain setelah fix...]');
await new Promise(r => setTimeout(r, 2000));
const files = [
'/_next/static/chunks/3km6z-n6wbgrs.js',
'/_next/static/chunks/1u6dw_tm45utz.css',
'/_next/static/chunks/1oskchnhjm4n8.css',
'/_next/static/chunks/21luguo5_g2uu.js',
'/_next/static/chunks/1-8s9_t85wwr4.js',
];
for (const f of files) {
const r = await httpGet(`https://demoplace.my.id${f}`);
const icon = r.status === 200 ? '✅' : '❌';
const ext = f.endsWith('.css') ? 'CSS' : 'JS ';
console.log(` ${icon} [${ext}] HTTP ${r.status} | ${r.size || 0} bytes | ${r.type || 'N/A'}`);
if (r.status !== 200) console.log(` ${f}`);
}
console.log('\n✅ Selesai! Lakukan HARD REFRESH (Ctrl+Shift+R) di browser.\n');
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+89
View File
@@ -0,0 +1,89 @@
// scripts/fix-turbopack-external.js
// Fix: Buat wrapper module untuk hashed external modules yang diperlukan Turbopack
'use strict';
const { Client } = require('ssh2');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const PUB = '/home/adminbackend/web/demoplace.my.id/public_html';
function exec(conn, cmd, label) {
if (label) console.log('\n[' + label + ']');
console.log('$ ' + cmd.substring(0, 90));
return new Promise(resolve => {
conn.exec(cmd, (err, s) => {
if (err) { resolve(''); return; }
let out = '';
s.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); })
.on('close', () => resolve(out));
});
});
}
async function main() {
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(SSH); });
console.log('[SSH] Connected\n');
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const NM = PUB + '/node_modules';
const SA = PUB + '/.next/standalone';
// 1. Verifikasi mongoose asli tersedia di public_html/node_modules
await exec(conn, 'ls ' + NM + '/mongoose/package.json 2>/dev/null && echo "mongoose OK" || echo "mongoose TIDAK ADA"', '1. Cek mongoose asli');
// 2. Cek error log untuk nama hash yang dipakai
await exec(conn, 'grep "mongoose-" ' + PUB + '/logs/frontend-error.log 2>/dev/null | grep "Cannot find" | tail -5', '2. Hash mongoose dari error log');
// 3. Buat wrapper module mongoose-8b99e611e7552af3
const HASH_PKG = NM + '/mongoose-8b99e611e7552af3';
await exec(conn,
'mkdir -p ' + HASH_PKG + ' && ' +
'echo \'{"name":"mongoose-8b99e611e7552af3","version":"1.0.0","main":"index.js"}\' > ' + HASH_PKG + '/package.json && ' +
'echo \'module.exports = require("mongoose");\' > ' + HASH_PKG + '/index.js && ' +
'echo "Wrapper module dibuat"',
'3. Buat wrapper mongoose-8b99e611e7552af3'
);
// 4. Verifikasi wrapper
await exec(conn, 'cat ' + HASH_PKG + '/index.js', '4. Isi wrapper module');
// 5. Juga cek apakah ada hash lain yang mungkin gagal (better-sqlite3)
await exec(conn,
'grep "Cannot find module" ' + PUB + '/logs/frontend-error.log 2>/dev/null | grep -v mongoose | tail -5',
'5. Cek module lain yang mungkin gagal'
);
// 6. Cek standalone/node_modules - apakah ada?
await exec(conn,
'ls -la ' + SA + '/ | grep node_modules || echo "standalone/node_modules TIDAK ADA"',
'6. Cek standalone/node_modules'
);
// 7. Buat standalone/node_modules symlink ke public_html/node_modules
await exec(conn,
'[ -d ' + SA + '/node_modules ] && echo "Sudah ada" || ' +
'(ln -s ' + NM + ' ' + SA + '/node_modules && echo "Symlink node_modules dibuat")',
'7. Buat symlink standalone/node_modules → public_html/node_modules'
);
// 8. Restart frontend
await exec(conn, PM2 + ' restart backone-frontend --update-env && echo "RESTART OK"', '8. Restart frontend');
// 9. Tunggu startup
await new Promise(r => setTimeout(r, 8000));
// 10. Test agents page
await exec(conn,
'curl -sk -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/agents',
'9. Test /agents (HTTP code)'
);
// 11. Cek error log setelah restart
await exec(conn, 'tail -10 ' + PUB + '/logs/frontend-error.log 2>/dev/null', '10. Error log setelah fix');
conn.end();
console.log('\n✅ Selesai! Refresh browser ke https://demoplace.my.id/agents\n');
}
main().catch(e => console.error('[FATAL]', e.message));
+211
View File
@@ -0,0 +1,211 @@
// scripts/hestia-local-api-fix.js
// ─────────────────────────────────────────────────────────────────────────────
// Fix 502: Panggil Hestia CP API dari localhost (via SSH curl) karena
// Hestia API memblokir IP eksternal tapi MENGIZINKAN localhost (127.0.0.1)
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const SSH = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
const DOMAIN = 'demoplace.my.id';
const WEB_USER = 'adminbackend';
const CONF = `/home/${WEB_USER}/conf/web/${DOMAIN}`;
const HESTIA_PASS = 'htEo7x6LsBQiEHHH';
function sshExec(conn, cmd, label = '') {
if (label) console.log(`\n[${label}]`);
console.log(`$ ${cmd.substring(0, 120)}${cmd.length > 120 ? '...' : ''}`);
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '';
stream.on('close', () => resolve(out))
.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); });
});
});
}
// Isi nginx.conf_custom yang akan kita tulis
const NGINX_CUSTOM = `# BackOne DPI - Proxy langsung ke Next.js port 3000
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade \\$http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Host \\$host;
proxy_set_header X-Real-IP \\$remote_addr;
proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \\$scheme;
proxy_read_timeout 86400;
}`;
const NGINX_SSL_CUSTOM = `# BackOne DPI - Proxy SSL langsung ke Next.js port 3000
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade \\$http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Host \\$host;
proxy_set_header X-Real-IP \\$remote_addr;
proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_read_timeout 86400;
}`;
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix 502 via Hestia localhost API ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
const conn = new Client();
await new Promise((resolve, reject) => { conn.on('ready', resolve).on('error', reject).connect(SSH); });
console.log('[SSH] Terhubung!\n');
// ── Step 1: Baca apache2.conf untuk diagnosa ───────────────────────────
await sshExec(conn, `cat ${CONF}/apache2.conf`, 'STEP 1: Baca Apache Config');
// ── Step 2: Cek apakah mod_proxy aktif di Apache ───────────────────────
await sshExec(conn,
`apache2ctl -M 2>/dev/null | grep -i proxy || ` +
`apachectl -M 2>/dev/null | grep -i proxy || ` +
`echo "Tidak bisa cek Apache modules"`,
'STEP 2: Cek mod_proxy'
);
// ── Step 3: Test Hestia API dari localhost (bypass IP restriction) ─────
console.log('\n[STEP 3: Hestia API dari localhost]');
// Generate SHA-256 hash untuk password
const hashCmd = `echo -n '${HESTIA_PASS}' | sha256sum | cut -d' ' -f1`;
console.log(`$ ${hashCmd}`);
const hashResult = await new Promise((resolve, reject) => {
conn.exec(hashCmd, (err, stream) => {
if (err) return reject(err);
let out = '';
stream.on('close', () => resolve(out.trim()))
.on('data', d => { out += d; })
.stderr.on('data', () => {});
});
});
console.log(`Hash: ${hashResult}`);
// Panggil Hestia API dari localhost untuk list proxy templates
await sshExec(conn,
`curl -sk -X POST https://localhost:8083/api/ ` +
`-d "hash=${hashResult}&cmd=v-list-web-templates-proxy&returncode=json" | head -c 500`,
'STEP 3a: List proxy templates via localhost API'
);
// Panggil Hestia API untuk restart web
await sshExec(conn,
`curl -sk -X POST https://localhost:8083/api/ ` +
`-d "hash=${hashResult}&cmd=v-restart-web&returncode=json"`,
'STEP 3b: Restart web via localhost API'
);
// ── Step 4: Tulis file via Hestia API run command ──────────────────────
// v-run-cli-cmd jalankan perintah shell sebagai root via Hestia
const writeHttpConf = `tee ${CONF}/nginx.conf_custom << 'NGINXEOF'\n${NGINX_CUSTOM}\nNGINXEOF`;
const encodedWrite = Buffer.from(writeHttpConf).toString('base64');
await sshExec(conn,
`curl -sk -X POST https://localhost:8083/api/ ` +
`-d "hash=${hashResult}&cmd=v-run-cli-cmd&arg1=tee&arg2=${CONF}/nginx.conf_custom" ` +
`--data-urlencode "stdin=${NGINX_CUSTOM}" | head -c 200`,
'STEP 4a: Tulis nginx.conf_custom via API'
);
// Fallback: Coba tee dengan python sebagai cara alternatif
await sshExec(conn,
`curl -sk -X POST https://localhost:8083/api/ ` +
`-d "hash=${hashResult}&cmd=v-add-web-domain-nginx-cfg&arg1=${WEB_USER}&arg2=${DOMAIN}" | head -c 200`,
'STEP 4b: Add nginx cfg via Hestia API'
);
// ── Step 5: Coba ubah Web Template ke yang support Node.js ───────────
// Template yang biasanya ada di Hestia untuk Node.js/proxy
for (const tpl of ['nodejs', 'node', 'proxy', 'default-nodejs']) {
const res = await new Promise(resolve => {
conn.exec(
`curl -sk -X POST https://localhost:8083/api/ ` +
`-d "hash=${hashResult}&cmd=v-change-web-domain-tpl&arg1=${WEB_USER}&arg2=${DOMAIN}&arg3=${tpl}&arg4=yes" | head -c 200`,
(err, stream) => {
if (err) return resolve('SSH Error');
let out = '';
stream.on('close', () => resolve(out))
.on('data', d => out += d)
.stderr.on('data', () => {});
}
);
});
console.log(`\n[Template ${tpl}]: ${res.trim()}`);
if (res.includes('0') || res.includes('OK')) {
console.log(`✅ Template '${tpl}' berhasil diterapkan!`);
break;
}
}
// ── Step 6: Paksa reload Nginx dengan cara yang bisa dilakukan user ───
await sshExec(conn,
`/usr/local/hestia/bin/v-restart-web 2>&1 || ` +
`sudo nginx -s reload 2>&1 || ` +
`curl -sk -X POST https://localhost:8083/api/ -d "hash=${hashResult}&cmd=v-restart-web" | head -c 100`,
'STEP 6: Reload Nginx'
);
// ── Step 7: Tunggu dan test domain ────────────────────────────────────
console.log('\n[STEP 7: Test domain setelah 8 detik...]');
await sshExec(conn,
`sleep 8 && curl -sk -o /dev/null -w "HTTPS Status: %{http_code}" https://${DOMAIN}/login`,
'Domain Test'
);
// ── Step 8: Tampilkan PM2 status ──────────────────────────────────────
await sshExec(conn,
`/home/adminbackend/.npm-global/bin/pm2 list`,
'STEP 8: PM2 Status'
);
conn.end();
// Test dari luar
console.log('\n[Test dari jaringan lokal...]');
const extResult = await new Promise(resolve => {
const req = https.get(`https://${DOMAIN}/login`,
{ timeout: 10000, rejectUnauthorized: false },
res => resolve({ status: res.statusCode, location: res.headers.location })
);
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
const icon = extResult.status >= 200 && extResult.status < 400 ? '✅' : '❌';
console.log(`\n ${icon} https://${DOMAIN}/login → HTTP ${extResult.status} ${extResult.error || ''}`);
if (extResult.status >= 200 && extResult.status < 400) {
console.log('\n🎉 SUKSES! Dashboard BackOne sudah online!\n');
} else {
console.log('\n⚠️ Masih 502. Perlu konfigurasi manual di Hestia CP panel.\n');
console.log('━━━━ INSTRUKSI MANUAL ━━━━');
console.log('1. Buka Hestia CP → Web → demoplace.my.id → Edit');
console.log('2. Di bagian "Proxy Template" — pilih template Node.js (jika ada)');
console.log(' ATAU di File Manager: navigasi ke /conf/web/demoplace.my.id/');
console.log(' Buat file baru: nginx.conf_custom dengan isi:');
console.log('\n' + NGINX_CUSTOM.replace(/\\$/g, '$'));
console.log('\n Dan nginx.ssl.conf_custom dengan isi:');
console.log('\n' + NGINX_SSL_CUSTOM.replace(/\\$/g, '$'));
console.log('\n3. Setelah file dibuat, klik Rebuild di Hestia CP Web Domain.');
}
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+180
View File
@@ -0,0 +1,180 @@
// scripts/hestia-nginx-fix.js
// ─────────────────────────────────────────────────────────────────────────────
// Fix 502 Bad Gateway dengan cara:
// 1. Cek permissions direktori conf Nginx
// 2. Gunakan Hestia CP REST API untuk ubah proxy template ke Node.js
// 3. Atau tulis nginx.conf_custom via Hestia API command
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client } = require('ssh2');
const https = require('https');
const querystring = require('querystring');
const crypto = require('crypto');
const SSH = { host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 60000 };
const HESTIA_HOST = 'isp.proit.id';
const HESTIA_PORT = 8083;
const HESTIA_USER = 'admin';
const HESTIA_PASS = 'htEo7x6LsBQiEHHH';
const DOMAIN = 'demoplace.my.id';
const WEB_USER = 'adminbackend';
const CONF = `/home/${WEB_USER}/conf/web/${DOMAIN}`;
// ── Hestia CP API call ─────────────────────────────────────────────────────
function hestiaApi(cmd, args = []) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256').update(HESTIA_PASS).digest('hex');
const body = querystring.stringify({ hash, cmd, ...Object.fromEntries(args.map((a, i) => [`arg${i + 1}`, a])) });
const req = https.request({
hostname: HESTIA_HOST,
port: HESTIA_PORT,
path: '/api/',
method: 'POST',
rejectUnauthorized: false,
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(body) }
}, res => {
let data = '';
res.on('data', d => data += d);
res.on('end', () => resolve({ status: res.statusCode, body: data.trim() }));
});
req.on('error', reject);
req.write(body);
req.end();
});
}
// ── SSH command runner ─────────────────────────────────────────────────────
function sshExec(conn, cmd) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) return reject(err);
let out = '';
stream.on('close', () => resolve(out))
.on('data', d => { out += d; process.stdout.write(d.toString()); })
.stderr.on('data', d => { out += d; process.stdout.write(d.toString()); });
});
});
}
// ── Custom Nginx config content ───────────────────────────────────────────
const nginxCustomContent = `# BackOne DPI — Proxy to Next.js port 3000
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}`;
const nginxSslContent = `# BackOne DPI — Proxy SSL to Next.js port 3000
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_read_timeout 86400;
}`;
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix 502 via Hestia API + Nginx Fix ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// ── Step 1: Diagnose conf directory permissions via SSH ─────────────────
console.log('\n▶ Step 1: Check conf directory permissions via SSH...');
const conn = new Client();
await new Promise((resolve, reject) => { conn.on('ready', resolve).on('error', reject).connect(SSH); });
console.log('[SSH] Connected!\n');
await sshExec(conn, `echo "=== CONF DIR PERMS ===" && ls -la /home/${WEB_USER}/conf/web/`);
await sshExec(conn, `echo "=== DOMAIN CONF DIR ===" && ls -la ${CONF}/`);
await sshExec(conn, `echo "=== EXISTING CUSTOM CONF ===" && ls -la ${CONF}/nginx.conf_* 2>/dev/null || echo "No custom conf yet"`);
// ── Step 2: Try Python to write the file (avoids shell quoting issues) ──
console.log('\n▶ Step 2: Attempting to write nginx.conf_custom via Python...');
const pyScript = `python3 -c "
import os
content = '''${nginxCustomContent.replace(/'/g, "\\'")}'''
path = '${CONF}/nginx.conf_custom'
try:
with open(path, 'w') as f:
f.write(content + '\\n')
print('[OK] Written: ' + path)
except Exception as e:
print('[FAIL] ' + str(e))
"`;
await sshExec(conn, pyScript);
const pyScriptSsl = `python3 -c "
content = '''${nginxSslContent.replace(/'/g, "\\'")}'''
path = '${CONF}/nginx.ssl.conf_custom'
try:
with open(path, 'w') as f:
f.write(content + '\\n')
print('[OK] Written: ' + path)
except Exception as e:
print('[FAIL] ' + str(e))
"`;
await sshExec(conn, pyScriptSsl);
conn.end();
// ── Step 3: Try Hestia CP REST API to restart web ─────────────────────
console.log('\n▶ Step 3: Call Hestia CP REST API to restart web server...');
// Try listing available proxy templates first
console.log('\n → Listing available proxy templates...');
const templatesRes = await hestiaApi('v-list-web-templates-proxy', []);
console.log(` API Response [list-proxy-templates]: HTTP ${templatesRes.status}`);
console.log(` Body: ${templatesRes.body.substring(0, 300)}`);
// Try to restart web via API
console.log('\n → Restarting web server via Hestia API...');
const restartRes = await hestiaApi('v-restart-web', []);
console.log(` API Response [restart-web]: HTTP ${restartRes.status}`);
console.log(` Body: ${restartRes.body}`);
// Try restart with correct user arg
const restartRes2 = await hestiaApi('v-restart-web', [WEB_USER]);
console.log(` API Response [restart-web user]: HTTP ${restartRes2.status}`);
console.log(` Body: ${restartRes2.body}`);
// ── Step 4: Wait and test domain ─────────────────────────────────────
console.log('\n▶ Step 4: Waiting 8 seconds then testing domain...');
await new Promise(r => setTimeout(r, 8000));
const testRes = await new Promise(resolve => {
const req = https.get('https://demoplace.my.id/login', { timeout: 10000, rejectUnauthorized: false }, res => {
resolve({ status: res.statusCode, location: res.headers.location });
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
const icon = testRes.status >= 200 && testRes.status < 400 ? '✅' : '❌';
console.log(`\n ${icon} https://demoplace.my.id/login → HTTP ${testRes.status} ${testRes.error || ''}`);
if (testRes.location) console.log(` Location: ${testRes.location}`);
if (testRes.status >= 200 && testRes.status < 400) {
console.log('\n🎉 SUKSES! Domain sudah bisa diakses!\n');
} else {
console.log('\n⚠️ Domain masih belum bisa diakses. Perlu konfig manual via Hestia panel.\n');
console.log('Silakan buka Hestia CP File Manager dan buat file:');
console.log(` ${CONF}/nginx.conf_custom`);
console.log(` ${CONF}/nginx.ssl.conf_custom`);
console.log('\nDengan isi (untuk keduanya):');
console.log(nginxCustomContent);
console.log('\nKemudian di Hestia CP: Web → demoplace.my.id → Rebuild\n');
}
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+126
View File
@@ -0,0 +1,126 @@
// scripts/migrate_production_config.js
if (typeof globalThis.crypto === 'undefined') {
globalThis.crypto = require('crypto');
}
const { MongoClient } = require('mongodb');
const path = require('path');
const LOCAL_URI = 'mongodb://127.0.0.1:27017';
const REMOTE_URI = 'mongodb://backone_user:SusuKudaLiar@mongodb.prod.proit.id:27017/backone_dpi?authSource=backone_dpi';
const DB_NAME = 'backone_dpi';
async function migrate() {
console.log("=============================================================");
console.log(" BackOne DPI - Selective Configuration Migration to Prod ");
console.log("=============================================================\n");
let localClient, remoteClient;
try {
console.log("Connecting to local MongoDB...");
localClient = new MongoClient(LOCAL_URI, { serverSelectionTimeoutMS: 3000 });
await localClient.connect();
const localDb = localClient.db(DB_NAME);
console.log("✓ Connected to local MongoDB.");
console.log("Connecting to production MongoDB...");
remoteClient = new MongoClient(REMOTE_URI, { serverSelectionTimeoutMS: 5000 });
await remoteClient.connect();
const remoteDb = remoteClient.db(DB_NAME);
console.log("✓ Connected to production MongoDB.\n");
// 1. Sync users (Upsert based on username to preserve existing user credentials if updated)
console.log("--- Syncing 'users' collection ---");
const localUsersColl = localDb.collection('users');
const remoteUsersColl = remoteDb.collection('users');
const localUsers = await localUsersColl.find({}).toArray();
console.log(`Found ${localUsers.length} users in local database.`);
let userUpsertCount = 0;
for (const user of localUsers) {
// Remove _id to avoid duplicate key errors or mismatched ObjectId
const { _id, ...userData } = user;
const res = await remoteUsersColl.updateOne(
{ username: user.username },
{ $set: userData },
{ upsert: true }
);
if (res.upsertedCount > 0 || res.modifiedCount > 0) {
userUpsertCount++;
}
}
console.log(`✓ Synchronized ${userUpsertCount} users to production MongoDB.`);
// 2. Sync tenantconfigs (Clear & Replace)
console.log("\n--- Syncing 'tenantconfigs' collection ---");
const localConfigColl = localDb.collection('tenantconfigs');
const remoteConfigColl = remoteDb.collection('tenantconfigs');
const localConfigs = await localConfigColl.find({}).toArray();
console.log(`Found ${localConfigs.length} tenant configurations locally.`);
if (localConfigs.length > 0) {
await remoteConfigColl.deleteMany({});
const cleanedConfigs = localConfigs.map(c => {
const { _id, ...rest } = c;
return rest;
});
await remoteConfigColl.insertMany(cleanedConfigs);
console.log(`✓ Synchronized ${localConfigs.length} configurations to production.`);
}
// 3. Sync customagentlocations (Clear & Replace)
console.log("\n--- Syncing 'customagentlocations' collection ---");
const localLocColl = localDb.collection('customagentlocations');
const remoteLocColl = remoteDb.collection('customagentlocations');
const localLocs = await localLocColl.find({}).toArray();
console.log(`Found ${localLocs.length} agent locations locally.`);
if (localLocs.length > 0) {
await remoteLocColl.deleteMany({});
const cleanedLocs = localLocs.map(l => {
const { _id, ...rest } = l;
return rest;
});
await remoteLocColl.insertMany(cleanedLocs);
console.log(`✓ Synchronized ${localLocs.length} agent locations to production.`);
}
// 4. Sync blacklistrules (Clear & Replace, if exists)
console.log("\n--- Syncing 'blacklistrules' collection ---");
const localRulesColl = localDb.collection('blacklistrules');
const remoteRulesColl = remoteDb.collection('blacklistrules');
// Check if collection exists
const localColls = await localDb.listCollections({ name: 'blacklistrules' }).toArray();
if (localColls.length > 0) {
const localRules = await localRulesColl.find({}).toArray();
console.log(`Found ${localRules.length} blacklist rules locally.`);
if (localRules.length > 0) {
await remoteRulesColl.deleteMany({});
const cleanedRules = localRules.map(r => {
const { _id, ...rest } = r;
return rest;
});
await remoteRulesColl.insertMany(cleanedRules);
console.log(`✓ Synchronized ${localRules.length} blacklist rules to production.`);
}
} else {
console.log("No blacklist rules found locally.");
}
console.log("\n=============================================================");
console.log("✓ Database configuration migration completed successfully!");
console.log("=============================================================");
} catch (err) {
console.error("\n✗ Migration failed:", err.message);
process.exit(1);
} finally {
if (localClient) await localClient.close();
if (remoteClient) await remoteClient.close();
}
}
migrate();
+26
View File
@@ -0,0 +1,26 @@
// scripts/quick-diag.js
'use strict';
const { Client } = require('ssh2');
const conn = new Client();
conn.on('ready', () => {
const cmds = [
'echo "=== Port 3000 test ==" && curl -sk -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login',
'echo "" && echo "=== Nginx conf_custom ===" && cat /home/adminbackend/conf/web/demoplace.my.id/nginx.conf_custom 2>/dev/null || echo MISSING',
'echo "=== Nginx ssl conf_custom ===" && cat /home/adminbackend/conf/web/demoplace.my.id/nginx.ssl.conf_custom 2>/dev/null || echo MISSING',
'echo "=== .htaccess ===" && cat /home/adminbackend/web/demoplace.my.id/public_html/.htaccess',
'echo "=== PM2 status ===" && /home/adminbackend/.npm-global/bin/pm2 list 2>/dev/null | grep -E "(name|backone)"',
];
let i = 0;
function run() {
if (i >= cmds.length) { conn.end(); console.log('\nDone.'); return; }
const cmd = cmds[i++];
conn.exec(cmd, (err, s) => {
if (err) { console.error(err.message); run(); return; }
s.on('data', d => process.stdout.write(d.toString()))
.stderr.on('data', d => process.stdout.write(d.toString()))
.on('close', run);
});
}
run();
}).on('error', e => console.error(e.message))
.connect({ host: '103.185.47.52', port: 2222, username: 'adminbackend', password: 'htEo7x6LsBQiEHHH', readyTimeout: 30000 });
+174
View File
@@ -0,0 +1,174 @@
// scripts/restore-proxy.js
// Pulihkan proxy.php + htaccess yang rusak via SFTP
'use strict';
const SftpClient = require('ssh2-sftp-client');
const { Client } = require('ssh2');
const https = require('https');
const CONFIG = {
host: '103.185.47.52', port: 2222,
username: 'adminbackend', password: 'htEo7x6LsBQiEHHH',
readyTimeout: 60000
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
// ── PHP Proxy (forward semua ke Next.js port 3000) ──────────────────────
const PROXY_PHP = `<?php
/**
* BackOne DPI - PHP Reverse Proxy
* Meneruskan semua request ke Next.js port 3000
*/
define('NEXTJS_URL', 'http://127.0.0.1:3000');
$uri = $_SERVER['REQUEST_URI'] ?? '/';
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$target = NEXTJS_URL . $uri;
$fwdHeaders = [
'X-Forwarded-For: ' . ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'),
'X-Forwarded-Proto: https',
'X-Real-IP: ' . ($_SERVER['REMOTE_ADDR'] ?? ''),
'Host: ' . ($_SERVER['HTTP_HOST'] ?? 'demoplace.my.id'),
];
$allHeaders = getallheaders() ?: [];
$skip = ['host','connection','transfer-encoding','keep-alive','content-length'];
foreach ($allHeaders as $name => $value) {
if (!in_array(strtolower($name), $skip)) {
$fwdHeaders[] = "$name: $value";
}
}
$body = in_array($method, ['POST','PUT','PATCH','DELETE']) ? file_get_contents('php://input') : null;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $target,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $fwdHeaders,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_ENCODING => '',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false) {
http_response_code(502);
header('Content-Type: text/plain');
echo 'BackOne: Gagal terhubung ke Next.js. Error: ' . $curlError;
exit;
}
$responseHeaders = substr($response, 0, $headerSize);
$responseBody = substr($response, $headerSize);
http_response_code($httpCode ?: 200);
$skipRes = ['transfer-encoding','connection','keep-alive','content-encoding'];
foreach (explode("\\r\\n", $responseHeaders) as $header) {
$header = trim($header);
if (empty($header) || preg_match('/^HTTP\\//i', $header)) continue;
$colonPos = strpos($header, ':');
if ($colonPos === false) continue;
$headerName = strtolower(trim(substr($header, 0, $colonPos)));
if (in_array($headerName, $skipRes)) continue;
header($header, false);
}
echo $responseBody;
`;
// ── .htaccess (routing ke proxy.php) ──────────────────────────────────
const HTACCESS = `# BackOne DPI — Apache .htaccess
# Redirect HTTP ke HTTPS
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Static Next.js files: sajikan langsung dari disk
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
# Semua request lain: teruskan ke PHP proxy (Next.js port 3000)
RewriteRule ^(.*)$ /proxy.php [L,QSA]
`;
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Pulihkan proxy.php & .htaccess ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// ── Upload via SFTP ──────────────────────────────────────────────────
console.log('▶ Upload proxy.php dan .htaccess via SFTP...');
const sftp = new SftpClient();
await sftp.connect(CONFIG);
await sftp.put(Buffer.from(PROXY_PHP, 'utf8'), `${ROOT}/proxy.php`);
console.log(' ✅ proxy.php diupload');
await sftp.put(Buffer.from(HTACCESS, 'utf8'), `${ROOT}/.htaccess`);
console.log(' ✅ .htaccess diupload (dengan newline yang benar)');
await sftp.end();
// ── Verifikasi via SSH ───────────────────────────────────────────────
console.log('\n▶ Verifikasi di server...');
const conn = new Client();
await new Promise((r, j) => { conn.on('ready', r).on('error', j).connect(CONFIG); });
console.log('[SSH] Terhubung!\n');
await new Promise((resolve) => {
conn.exec(
`echo "=== .htaccess ===" && cat ${ROOT}/.htaccess && ` +
`echo "" && echo "=== proxy.php (5 baris awal) ===" && head -5 ${ROOT}/proxy.php && ` +
`echo "" && echo "=== Test port 3000 ===" && curl -sk -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login && echo "" && ` +
`echo "=== Test port 8009/8080 ===" && curl -sk -o /dev/null -w "Apache 8009: %{http_code}" http://127.0.0.1:8009/ && echo "" && curl -sk -o /dev/null -w "Apache 8080: %{http_code}" http://103.185.47.52:8080/login && echo ""`,
(err, s) => {
if (err) { resolve(); return; }
s.on('data', d => process.stdout.write(d.toString()))
.stderr.on('data', d => process.stdout.write(d.toString()))
.on('close', () => { resolve(); });
}
);
});
conn.end();
// ── Test external ────────────────────────────────────────────────────
console.log('\n▶ Test domain dari luar (tunggu 3 detik)...');
await new Promise(r => setTimeout(r, 3000));
for (const path of ['/', '/login', '/api/health']) {
const r = await new Promise(resolve => {
const req = https.get(`https://demoplace.my.id${path}`,
{ timeout: 12000, rejectUnauthorized: false },
res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, body: body.substring(0, 80), location: res.headers.location }));
}
);
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
const icon = r.status === 200 ? '✅' : r.status === 301 || r.status === 307 ? '↩️ ' : '❌';
console.log(` ${icon} https://demoplace.my.id${path} → HTTP ${r.status} ${r.error || ''}`);
if (r.location) console.log(` → ${r.location}`);
}
console.log('\n✅ Selesai! Refresh https://demoplace.my.id/login\n');
}
main().catch(err => { console.error('[FATAL]', err.message); process.exit(1); });
+153
View File
@@ -0,0 +1,153 @@
// scripts/security-fix.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Security fix & final production configuration
// 1. Remove .env.production from standalone directory (security)
// 2. Fix PM2 to use new ecosystem config properly
// 3. Verify HTTPS redirect and domain reachability
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const https = require('https');
const http = require('http');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const COMMANDS = [
// ── CRITICAL SECURITY: Remove .env files from Next.js standalone build ────
`echo "=== SECURITY FIX: Remove .env from standalone ==="`,
`rm -f ${ROOT}/.next/standalone/.env.production && echo "REMOVED: .env.production from standalone"`,
`rm -f ${ROOT}/.next/standalone/.env.local && echo "REMOVED: .env.local from standalone (if existed)"`,
// Verify removal
`echo "=== VERIFY: No .env in standalone ===" && find ${ROOT}/.next/standalone -name ".env*" 2>/dev/null | head -5 || echo "CLEAN: No .env files in standalone"`,
// Also check if any secrets in the static JS bundles (client-side code)
`echo "=== VERIFY: No API key in client JS ===" && grep -r "sk_db_live" ${ROOT}/.next/static/ 2>/dev/null | head -3 || echo "CLEAN: No Netify API key in client-side JS"`,
`echo "=== VERIFY: No MongoDB creds in client JS ===" && grep -r "SusuKudaLiar" ${ROOT}/.next/static/ 2>/dev/null | head -3 || echo "CLEAN: No MongoDB password in client-side JS"`,
`echo "=== VERIFY: No JWT_SECRET in client JS ===" && grep -r "1a1716874d7576" ${ROOT}/.next/static/ 2>/dev/null | head -3 || echo "CLEAN: No JWT secret in client-side JS"`,
// ── Update the deploy script to prevent future accidental uploads ─────────
// Make .env.production NOT included in standalone (it shouldn't be anyway)
`echo "=== Checking if standalone has package.json with correct env ===" && cat ${ROOT}/.next/standalone/package.json 2>/dev/null | head -5`,
// ── Fix ecosystem.config.js to use correct cwd and path ──────────────────
// The PM2 frontend shows version 0.1.0 (old package.json from inside standalone)
// The cwd in ecosystem.config.js points to the root, which is correct
`echo "=== Current ecosystem.config.js ===" && cat ${ROOT}/ecosystem.config.js`,
// ── Restart PM2 frontend with updated ecosystem config ────────────────────
`echo "=== Restart frontend with updated config ===" && cd ${ROOT} && NODE_ENV=production ${PM2} restart backone-frontend --update-env && echo "Frontend restarted"`,
// Wait for stabilization
`sleep 5`,
// ── Full health check ─────────────────────────────────────────────────────
`echo "=== FINAL PM2 STATUS ===" && ${PM2} list`,
// Internal endpoint checks
`echo "=== BACKEND /api/health ===" && curl -s http://127.0.0.1:3001/api/health`,
`echo "=== FRONTEND / ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/`,
`echo "=== FRONTEND /login ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
// ── Test login API endpoint ────────────────────────────────────────────────
`echo ""`,
`echo "=== TEST LOGIN API ===" && curl -s -X POST http://127.0.0.1:3001/api/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin"}' | head -c 200`,
// ── Show proxy MongoDB connection ─────────────────────────────────────────
`echo ""`,
`echo "=== PROXY MONGODB STATUS ===" && ${PM2} logs backone-proxy --lines 20 --nostream 2>&1 | grep -E "(MongoDB|Connected|Capacity|Scheduler|Started|Mode)" | tail -10`,
// ── Check if cPanel is configured to serve on port 8083 ──────────────────
`echo "=== CPANEL PORT CHECK ===" && ss -tlnp 2>/dev/null | grep -E "(8083|80|443|3000)" | head -10`,
// ── Check Apache/nginx proxy config ──────────────────────────────────────
`echo "=== CHECK PROXY CONFIG ===" && cat /home/adminbackend/.htaccess 2>/dev/null | head -20 || echo "No .htaccess at home"`,
`echo "=== CHECK WEB ROOT HTACCESS ===" && cat ${ROOT}/.htaccess 2>/dev/null | head -20 || echo "No .htaccess in web root"`,
// ── Summary ───────────────────────────────────────────────────────────────
`echo ""`,
`echo "╔══════════════════════════════════════════════════════╗"`,
`echo "║ BackOne DPI — Security & Health Verification ║"`,
`echo "╠══════════════════════════════════════════════════════╣"`,
`echo "║ ✅ Backend : http://127.0.0.1:3001 (internal) ║"`,
`echo "║ ✅ Proxy : http://127.0.0.1:4000 (internal) ║"`,
`echo "║ ✅ Frontend : http://127.0.0.1:3000 (internal) ║"`,
`echo "║ ✅ MongoDB : mongodb.prod.proit.id:27017 ║"`,
`echo "║ 🌐 Domain : https://demoplace.my.id ║"`,
`echo "╚══════════════════════════════════════════════════════╝"`,
];
function runSsh(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
console.log('[SSH] Connected!\n');
let i = 0;
function next() {
if (i >= commands.length) { ssh.end(); return; }
const cmd = commands[i++];
console.log(`\n$ ${cmd.substring(0, 130)}${cmd.length > 130 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error('[ERR]', err.message); next(); return; }
stream.on('data', d => process.stdout.write(d.toString()));
stream.stderr.on('data', d => { const t = d.toString(); if (!t.includes('npm warn') && !t.includes('notice')) process.stdout.write(t); });
stream.on('close', next);
});
}
next();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
// Check HTTPS domain
function checkHttps(url) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 10000, rejectUnauthorized: false }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, body: body.substring(0, 200), location: res.headers.location }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Security Fix & Final Verification ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
await runSsh(COMMANDS);
// Check HTTPS
console.log('\n▶ Testing HTTPS access...\n');
const urls = [
'https://demoplace.my.id/',
'https://demoplace.my.id/login',
'https://demoplace.my.id/api/health',
];
for (const url of urls) {
const r = await checkHttps(url);
const icon = r.status >= 200 && r.status < 400 ? '✅' : r.status === 0 ? '⚠️' : '❌';
console.log(` ${icon} ${url} → HTTP ${r.status} ${r.error || ''}`);
if (r.location) console.log(` Redirects to: ${r.location}`);
if (r.body && r.status === 200) console.log(` Body: ${r.body.substring(0, 80)}...`);
}
console.log('\n✅ Security fix & verification complete!\n');
}
main().catch(err => { console.error('[FATAL]', err); process.exit(1); });
+123
View File
@@ -0,0 +1,123 @@
// scripts/server-setup.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Server Investigation & PM2 Setup Script
// Runs SSH commands to check server state and install PM2 locally
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const REMOTE_ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
// Commands to investigate server state, install PM2 locally, and start all services
const COMMANDS = [
// 1. Check Node.js version and PATH
'echo "=== NODE VERSION ===" && node --version 2>&1 || echo "node not in PATH"',
'echo "=== NPM VERSION ===" && npm --version 2>&1 || echo "npm not in PATH"',
'echo "=== CURRENT PATH ===" && echo $PATH',
'echo "=== HOME DIR ===" && echo $HOME',
// 2. Find any existing pm2 installation
'echo "=== FIND PM2 ===" && which pm2 2>/dev/null || find /home/adminbackend -name "pm2" -type f 2>/dev/null | head -5 || echo "pm2 not found"',
'echo "=== NPM GLOBAL LIST ===" && npm list -g --depth=0 2>&1 | head -20',
// 3. Check currently running Node.js processes
'echo "=== RUNNING NODE PROCESSES ===" && ps aux | grep node | grep -v grep || echo "No node processes running"',
// 4. Check if cPanel Node.js App is configured
'echo "=== CPANEL NODEAPP CONFIG ===" && ls /home/adminbackend/.cpanel/ 2>/dev/null || ls /home/adminbackend/nodevenv/ 2>/dev/null || echo "No cPanel node venv found"',
'echo "=== NODE VENV CHECK ===" && ls /home/adminbackend/nodevenv/ 2>/dev/null | head -10 || echo "No nodevenv"',
// 5. Install PM2 locally to user home prefix (no root needed)
'echo "=== INSTALLING PM2 LOCALLY ===" && npm install pm2 --prefix /home/adminbackend/.local 2>&1 | tail -5',
'echo "=== PM2 PATH ===" && /home/adminbackend/.local/bin/pm2 --version 2>&1 || echo "pm2 local install failed"',
// 6. Kill any existing BackOne processes to avoid port conflicts
`echo "=== KILLING EXISTING PROCESSES ===" && pkill -f "proxy/index.js" 2>/dev/null || true && pkill -f "backend/server.js" 2>/dev/null || true && pkill -f "standalone/server.js" 2>/dev/null || true && sleep 2 && echo "Done"`,
// 7. Start backend server
`echo "=== STARTING BACKEND ===" && cd ${REMOTE_ROOT} && NODE_ENV=production $(cat .env.production | grep -v '^#' | grep -v '^$' | xargs) node backend/server.js > logs/backend-out.log 2>logs/backend-error.log &`,
// 8. Start proxy server
`echo "=== STARTING PROXY ===" && cd ${REMOTE_ROOT} && NODE_ENV=production node proxy/index.js > logs/proxy-out.log 2>logs/proxy-error.log &`,
// 9. Start Next.js frontend
`echo "=== STARTING FRONTEND ===" && cd ${REMOTE_ROOT} && NODE_ENV=production PORT=3000 HOSTNAME=127.0.0.1 node .next/standalone/server.js > logs/frontend-out.log 2>logs/frontend-error.log &`,
// 10. Wait and verify
'sleep 6',
// 11. Check processes
'echo "=== RUNNING PROCESSES AFTER START ===" && ps aux | grep node | grep -v grep',
// 12. Test health endpoints
'echo "=== BACKEND HEALTH ===" && curl -s http://127.0.0.1:3001/api/health 2>&1 || echo "Backend not responding"',
'echo "=== FRONTEND HEALTH ===" && curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/ 2>&1 || echo "Frontend not responding"',
// 13. Show recent logs
'echo "=== BACKEND LOG (last 10 lines) ===" && tail -10 /home/adminbackend/web/demoplace.my.id/public_html/logs/backend-out.log 2>/dev/null || echo "No backend log"',
'echo "=== PROXY LOG (last 5 lines) ===" && tail -5 /home/adminbackend/web/demoplace.my.id/public_html/logs/proxy-out.log 2>/dev/null || echo "No proxy log"',
];
function runSshCommands(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
console.log('[SSH] Connected to server!\n');
let index = 0;
function runNext() {
if (index >= commands.length) {
console.log('\n[SSH] All commands complete. Disconnecting...');
ssh.end();
return;
}
const cmd = commands[index++];
console.log(`\n$ ${cmd.substring(0, 100)}${cmd.length > 100 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) {
console.error(`[ERROR] ${err.message}`);
runNext();
return;
}
stream.on('data', (data) => process.stdout.write(data.toString()));
stream.stderr.on('data', (data) => process.stdout.write(`[ERR] ${data}`));
stream.on('close', () => runNext());
});
}
runNext();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Server Setup & Process Manager ║');
console.log('╚══════════════════════════════════════════════════╝\n');
try {
await runSshCommands(COMMANDS);
console.log('\n✅ Server setup complete!\n');
} catch (err) {
console.error('\n[FATAL]', err.message);
process.exit(1);
}
}
main();
+133
View File
@@ -0,0 +1,133 @@
// scripts/start-frontend.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Start Frontend & Fix PM2 Setup on Server
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const REMOTE_ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
const COMMANDS = [
// Check what's currently running on port 3000
'echo "=== PORT 3000 CHECK ===" && ss -tlnp 2>/dev/null | grep 3000 || netstat -tlnp 2>/dev/null | grep 3000 || echo "Port 3000 not in use"',
// Check if standalone server.js exists
`echo "=== STANDALONE CHECK ===" && ls -la ${REMOTE_ROOT}/.next/standalone/server.js 2>/dev/null || echo "standalone server.js NOT FOUND"`,
// List standalone directory structure
`echo "=== STANDALONE DIRECTORY ===" && ls ${REMOTE_ROOT}/.next/standalone/ 2>/dev/null | head -20`,
// Check static assets copied correctly
`echo "=== STATIC IN STANDALONE ===" && ls ${REMOTE_ROOT}/.next/standalone/.next/ 2>/dev/null || echo "No .next dir in standalone"`,
// Find the correct npm bin directory for npx pm2
'echo "=== NPM BIN DIR ===" && npm bin 2>/dev/null || npm config get prefix',
// Check npx pm2 path
'echo "=== NPX PM2 PATH ===" && find /home/adminbackend/.npm -name "pm2" -type f 2>/dev/null | grep "/bin/pm2" | head -3',
// Install PM2 using npm to user's npm prefix
'echo "=== SET NPM PREFIX ===" && npm config set prefix /home/adminbackend/.npm-global && npm install -g pm2 2>&1 | tail -5',
'echo "=== PM2 INSTALLED ===" && /home/adminbackend/.npm-global/bin/pm2 --version 2>&1 || echo "Still not found"',
// Try npx approach
`echo "=== USING NPX PM2 ===" && cd ${REMOTE_ROOT} && npx pm2 --version 2>&1`,
// Start frontend standalone server if not running
`echo "=== CHECKING STANDALONE SERVER ===" && ls -la ${REMOTE_ROOT}/.next/standalone/server.js`,
// Start with nohup (works on cPanel)
`echo "=== STARTING FRONTEND NOHUP ===" && cd ${REMOTE_ROOT} && NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0 NEXT_PUBLIC_API_URL=http://127.0.0.1:3001 nohup node .next/standalone/server.js >> logs/frontend-out.log 2>&1 & echo "PID: $!"`,
// Wait and check
'sleep 5',
// Verify port 3000 is now listening
'echo "=== PORT 3000 AFTER START ===" && ss -tlnp | grep 3000 || netstat -tlnp | grep 3000 || echo "Port 3000 still not in use"',
// Check all running processes
'echo "=== ALL NODE PROCESSES ===" && ps aux | grep node | grep -v grep',
// Test all endpoints
'echo "=== BACKEND HEALTH ===" && curl -s http://127.0.0.1:3001/api/health',
'echo "=== FRONTEND RESPONSE ===" && curl -s -o /dev/null -w "HTTP Status: %{http_code}" http://127.0.0.1:3000/ 2>&1',
// Show frontend log
`echo "=== FRONTEND LOG ===" && tail -20 ${REMOTE_ROOT}/logs/frontend-out.log 2>/dev/null || echo "No frontend log"`,
// Check proxy connection to MongoDB
`echo "=== PROXY LOG ===" && tail -10 ${REMOTE_ROOT}/logs/proxy-out.log 2>/dev/null || echo "No proxy log"`,
// Show existing pm2 in npx cache and start all services via npx pm2
`echo "=== STARTING WITH NPX PM2 ===" && cd ${REMOTE_ROOT} && NODE_ENV=production npx pm2 list 2>&1 | head -20`,
];
function runSshCommands(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
const results = [];
ssh.on('ready', () => {
console.log('[SSH] Connected!\n');
let index = 0;
function runNext() {
if (index >= commands.length) {
ssh.end();
return;
}
const cmd = commands[index++];
const preview = cmd.length > 120 ? cmd.substring(0, 120) + '...' : cmd;
console.log(`\n$ ${preview}`);
ssh.exec(cmd, { env: { PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' } }, (err, stream) => {
if (err) {
console.error(`[ERROR] ${err.message}`);
runNext();
return;
}
stream.on('data', (data) => process.stdout.write(data.toString()));
stream.stderr.on('data', (data) => {
const text = data.toString();
if (!text.includes('npm warn') && !text.includes('npm notice')) {
process.stdout.write(`[ERR] ${text}`);
}
});
stream.on('close', () => runNext());
});
}
runNext();
ssh.on('end', () => resolve(results));
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Frontend Startup & PM2 Fix ║');
console.log('╚══════════════════════════════════════════════════╝\n');
try {
await runSshCommands(COMMANDS);
console.log('\n✅ Frontend startup attempt complete!\n');
} catch (err) {
console.error('\n[FATAL]', err.message);
process.exit(1);
}
}
main();
+29
View File
@@ -0,0 +1,29 @@
const { MongoMemoryServer } = require('mongodb-memory-server');
async function start() {
try {
const mongod = await MongoMemoryServer.create({
instance: {
port: 27017,
dbName: 'backone_dpi'
}
});
console.log('[MONGO] 🚀 Local In-Memory MongoDB running at: mongodb://127.0.0.1:27017/backone_dpi');
const cleanup = async () => {
await mongod.stop();
process.exit(0);
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
} catch (err) {
if (err.code === 'EADDRINUSE' || (err.message && err.message.includes('EADDRINUSE'))) {
console.log('[MONGO] ℹ️ Port 27017 is already in use. Skipping in-memory MongoDB launch.');
} else {
console.error('[MONGO] ❌ Error starting MongoMemoryServer:', err.message);
}
}
}
start();
+132
View File
@@ -0,0 +1,132 @@
// scripts/update-pm2-config.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Update PM2 to use correct standalone script path
// The frontend PM2 process is currently running the OLD server.js from root.
// We need to delete and re-create the PM2 process with the correct script.
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const https = require('https');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const COMMANDS = [
// Show current PM2 state
`echo "=== CURRENT PM2 STATE ===" && ${PM2} list`,
`echo "=== CURRENT FRONTEND SCRIPT ===" && ${PM2} show backone-frontend 2>&1 | grep -E "(script|cwd)" | head -5`,
// Delete old frontend process and start fresh with correct script
`echo "=== DELETING OLD FRONTEND PROCESS ===" && ${PM2} delete backone-frontend && echo "Deleted"`,
// Start frontend using the new ecosystem config (which points to .next/standalone/server.js)
`echo "=== STARTING FRONTEND WITH NEW CONFIG ===" && cd ${ROOT} && NODE_ENV=production ${PM2} start ecosystem.config.js --only backone-frontend && echo "Started"`,
// Wait for startup
`sleep 6`,
// Verify new process
`echo "=== NEW PM2 STATUS ===" && ${PM2} list`,
`echo "=== NEW FRONTEND SCRIPT ===" && ${PM2} show backone-frontend 2>&1 | grep -E "(script|cwd|version|status)" | head -8`,
// Test frontend response
`echo "=== FRONTEND INTERNAL TEST ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/`,
`echo "=== FRONTEND LOGIN TEST ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
// Save PM2 state
`echo "=== SAVING PM2 STATE ===" && ${PM2} save && echo "PM2 state saved"`,
// Show frontend log to confirm new code
`echo "=== FRONTEND LOG ===" && ${PM2} logs backone-frontend --lines 10 --nostream 2>&1 | tail -15`,
// Check HTTPS now
`echo "=== HTTPS VIA CURL ===" && curl -s -o /dev/null -w "HTTP %{http_code}" -L --max-redirs 3 https://demoplace.my.id/ 2>&1 || echo "HTTPS not accessible from server itself"`,
// Check HTTP (which should redirect to HTTPS per .htaccess)
`echo "=== HTTP CHECK ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://demoplace.my.id/ 2>&1`,
// Check Apache config for this domain
`echo "=== APACHE VHOST CONFIG ===" && cat /etc/apache2/conf.d/userdata/ssl/2_4/adminbackend/demoplace.my.id/*.conf 2>/dev/null | head -30 || ls /etc/apache2/conf.d/userdata/ 2>/dev/null | head -5 || echo "No Apache vhost config accessible"`,
// Check cPanel proxy config
`echo "=== CPANEL PROXY CONF ===" && cat /home/adminbackend/.cpanel/ea4/php.conf 2>/dev/null || echo "No cPanel PHP conf"`,
`cat /home/adminbackend/etc/apache_conf 2>/dev/null || echo "No Apache conf in home"`,
// Final summary
`echo ""`,
`echo "╔══════════════════════════════════════════════════════╗"`,
`echo "║ BackOne DPI — PM2 Update Complete ║"`,
`${PM2} list 2>/dev/null | grep -E "(online|error)" | awk '{print "║ " $2 " " $10 " " $6 " ║"}'`,
`echo "╚══════════════════════════════════════════════════════╝"`,
];
function runSsh(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
console.log('[SSH] Connected!\n');
let i = 0;
function next() {
if (i >= commands.length) { ssh.end(); return; }
const cmd = commands[i++];
console.log(`\n$ ${cmd.substring(0, 130)}${cmd.length > 130 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error('[ERR]', err.message); next(); return; }
stream.on('data', d => process.stdout.write(d.toString()));
stream.stderr.on('data', d => { const t = d.toString(); if (!t.includes('npm warn') && !t.includes('notice')) process.stdout.write(t); });
stream.on('close', next);
});
}
next();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function checkHttps(url) {
return new Promise(resolve => {
const req = https.get(url, { timeout: 10000, rejectUnauthorized: false }, res => {
let body = '';
res.on('data', d => body += d);
res.on('end', () => resolve({ status: res.statusCode, location: res.headers.location, body: body.substring(0, 150) }));
});
req.on('error', e => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Fix PM2 Script & HTTPS Verification ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
await runSsh(COMMANDS);
console.log('\n\n▶ External HTTPS checks (from local machine)...\n');
const checks = [
'https://demoplace.my.id/',
'https://demoplace.my.id/login',
'https://demoplace.my.id/api/health',
];
for (const url of checks) {
const r = await checkHttps(url);
const icon = r.status >= 200 && r.status < 400 ? '✅' : r.status === 0 ? '⚠️' : '❌';
console.log(` ${icon} ${url} → HTTP ${r.status} ${r.error || ''}`);
if (r.location) console.log(` → Redirects to: ${r.location}`);
if (r.status === 200 && r.body) console.log(` Body preview: ${r.body.substring(0, 100)}`);
}
console.log('\n✅ Done!\n');
}
main().catch(err => { console.error('[FATAL]', err); process.exit(1); });
+87
View File
@@ -0,0 +1,87 @@
// scripts/upload-profile-pictures.js
// Upload semua foto profil dari lokal ke server production
'use strict';
const SftpClient = require('ssh2-sftp-client');
const { Client: SshClient } = require('ssh2');
const fs = require('fs');
const path = require('path');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
readyTimeout: 30000,
retries: 3,
retry_factor: 2,
retry_minTimeout: 2000,
};
const LOCAL_DIR = path.join(__dirname, '..', 'backend', 'public', 'api', 'uploads');
const REMOTE_DIR = '/home/adminbackend/web/demoplace.my.id/public_html/api/uploads';
function sshExec(cmd) {
return new Promise((resolve, reject) => {
const conn = new SshClient();
conn.on('ready', () => {
conn.exec(cmd, (err, stream) => {
if (err) { conn.end(); return reject(err); }
let out = '';
stream
.on('data', d => { out += d; })
.stderr.on('data', d => { out += d; });
stream.on('close', () => { conn.end(); resolve(out); });
});
}).on('error', reject)
.connect({ host: CONFIG.host, port: CONFIG.port, username: CONFIG.username, password: CONFIG.password, readyTimeout: CONFIG.readyTimeout });
});
}
async function main() {
if (!fs.existsSync(LOCAL_DIR)) {
console.log('Folder uploads lokal tidak ditemukan:', LOCAL_DIR);
return;
}
const files = fs.readdirSync(LOCAL_DIR).filter(f => /\.(png|jpg|jpeg|webp)$/i.test(f));
if (files.length === 0) {
console.log('Tidak ada file gambar di folder uploads lokal.');
return;
}
console.log(`Ditemukan ${files.length} file foto profil untuk diupload:\n ${files.join('\n ')}\n`);
// Buat direktori di server jika belum ada
await sshExec(`mkdir -p ${REMOTE_DIR} && chmod 755 ${REMOTE_DIR}`);
console.log(`[Server] Direktori ${REMOTE_DIR} siap\n`);
// Upload semua file via SFTP
const sftp = new SftpClient();
await sftp.connect(CONFIG);
console.log('[SFTP] Terhubung ke server\n');
let uploaded = 0;
let failed = 0;
for (const file of files) {
const localPath = path.join(LOCAL_DIR, file);
const remotePath = `${REMOTE_DIR}/${file}`;
try {
await sftp.put(localPath, remotePath);
console.log(`✅ Upload: ${file}`);
uploaded++;
} catch (err) {
console.error(`❌ Gagal upload ${file}:`, err.message);
failed++;
}
}
await sftp.end();
// Verifikasi
const remoteList = await sshExec(`ls -lh ${REMOTE_DIR}`);
console.log('\n[Server] Isi folder uploads:\n' + remoteList);
console.log(`\n✅ Selesai! ${uploaded} berhasil, ${failed} gagal.`);
}
main().catch(e => console.error('[FATAL]', e.message));
+147
View File
@@ -0,0 +1,147 @@
// scripts/upload-standalone-next.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Upload missing .next/standalone/.next/ directory to server
// This fixes: "Could not find a production build in the './.next' directory"
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const path = require('path');
const fs = require('fs');
const SftpClient = require('ssh2-sftp-client');
const { Client: SshClient } = require('ssh2');
const LOCAL_ROOT = path.resolve(__dirname, '..');
const REMOTE_ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
// Recursively upload a local directory to remote (skipping node_modules and .git)
async function uploadDir(sftp, localPath, remotePath) {
const items = fs.readdirSync(localPath, { withFileTypes: true });
try { await sftp.mkdir(remotePath, true); } catch {}
for (const item of items) {
if (['node_modules', '.git'].includes(item.name)) continue;
if (item.name.endsWith('.log')) continue;
const lp = path.join(localPath, item.name);
const rp = remotePath + '/' + item.name;
if (item.isDirectory()) {
process.stdout.write(` [DIR] ${rp}\n`);
await uploadDir(sftp, lp, rp);
} else {
process.stdout.write(` [UP] ${rp} ...`);
await sftp.put(lp, rp);
process.stdout.write(' done\n');
}
}
}
function runSsh(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
let i = 0;
function next() {
if (i >= commands.length) { ssh.end(); return; }
const cmd = commands[i++];
console.log(`\n$ ${cmd.substring(0, 120)}${cmd.length > 120 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error('[ERR]', err.message); next(); return; }
stream.on('data', d => process.stdout.write(d.toString()));
stream.stderr.on('data', d => { const t = d.toString(); if (!t.includes('npm warn') && !t.includes('notice')) process.stdout.write(t); });
stream.on('close', next);
});
}
next();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Upload Standalone Build Artifacts ║');
console.log('║ Uploading: .next/standalone/.next/ (BUILD_ID + server) ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
const localStandaloneNext = path.join(LOCAL_ROOT, '.next', 'standalone', '.next');
if (!fs.existsSync(localStandaloneNext)) {
console.error('✗ ERROR: .next/standalone/.next not found locally!');
console.error(' Run: npm run build');
process.exit(1);
}
// Verify BUILD_ID exists
const buildId = fs.existsSync(path.join(localStandaloneNext, 'BUILD_ID'))
? fs.readFileSync(path.join(localStandaloneNext, 'BUILD_ID'), 'utf8').trim()
: null;
console.log(`✓ Build ID: ${buildId || 'NOT FOUND'}`);
console.log(`✓ Local path: ${localStandaloneNext}`);
const sftp = new SftpClient();
try {
await sftp.connect(CONFIG);
console.log('[SFTP] Connected!\n');
const remotePath = `${REMOTE_ROOT}/.next/standalone/.next`;
console.log(`\n[DIR] Uploading .next/standalone/.next/ → ${remotePath}\n`);
await uploadDir(sftp, localStandaloneNext, remotePath);
console.log('\n[SFTP] Upload complete!\n');
} finally {
await sftp.end();
}
// Post-upload: restart PM2 frontend, verify
console.log('\n▶ Restarting PM2 frontend with new build...\n');
await runSsh([
// Verify BUILD_ID now exists on server
`echo "=== VERIFY BUILD_ID ===" && cat ${REMOTE_ROOT}/.next/standalone/.next/BUILD_ID`,
// Also verify static dir exists
`echo "=== VERIFY STATIC ===" && ls ${REMOTE_ROOT}/.next/standalone/.next/static/ 2>/dev/null | wc -l | xargs echo "Static files:"`,
// Restart frontend PM2 process
`echo "=== RESTARTING FRONTEND ===" && ${PM2} restart backone-frontend --update-env && echo "Restarted"`,
// Wait
`sleep 6`,
// Check PM2 status
`echo "=== PM2 STATUS ===" && ${PM2} list`,
// Test internal endpoints
`echo "=== FRONTEND / ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/`,
`echo "=== FRONTEND /login ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
`echo "=== BACKEND HEALTH ===" && curl -s http://127.0.0.1:3001/api/health`,
// Show frontend log
`echo "=== FRONTEND LOG ===" && ${PM2} logs backone-frontend --lines 10 --nostream 2>&1 | tail -15`,
// Test via domain (HTTP which redirects to HTTPS via .htaccess)
`echo "=== DOMAIN HTTP TEST ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://demoplace.my.id/`,
// Check port 3000
`echo "=== PORT 3000 ===" && ss -tlnp | grep 3000`,
]);
console.log('\n══════════════════════════════════════════════════════════');
console.log(' ✅ Standalone build artifacts uploaded & frontend restarted!');
console.log(' 🌐 Dashboard: https://demoplace.my.id');
console.log('══════════════════════════════════════════════════════════\n');
}
main().catch(err => {
console.error('\n[FATAL]', err.message);
process.exit(1);
});
+148
View File
@@ -0,0 +1,148 @@
// scripts/verify-deployment.js
// ─────────────────────────────────────────────────────────────────────────────
// BackOne DPI — Post-Deployment Verification Script
// Tests: frontend, backend, security headers, proxy, MongoDB
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const { Client: SshClient } = require('ssh2');
const https = require('https');
const http = require('http');
const CONFIG = {
host: '103.185.47.52',
port: 2222,
username: 'adminbackend',
password: 'htEo7x6LsBQiEHHH',
};
const PM2 = '/home/adminbackend/.npm-global/bin/pm2';
const ROOT = '/home/adminbackend/web/demoplace.my.id/public_html';
// HTTP request helper
function httpGet(url) {
return new Promise((resolve) => {
const mod = url.startsWith('https') ? https : http;
const req = mod.get(url, { timeout: 10000 }, (res) => {
let body = '';
res.on('data', (d) => body += d);
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: body.substring(0, 300) }));
});
req.on('error', (e) => resolve({ status: 0, error: e.message }));
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
});
}
const SSH_COMMANDS = [
// Check PM2 process list
`echo "=== PM2 STATUS ===" && ${PM2} list`,
// Check backend health
`echo "=== BACKEND HEALTH ===" && curl -s http://127.0.0.1:3001/api/health`,
// Check frontend pages
`echo "=== FRONTEND / ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/`,
`echo "=== FRONTEND /login ===" && curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:3000/login`,
// Security audit: ensure backend and proxy ports are NOT publicly accessible
`echo "=== SECURITY: Port 3001 external test ===" && ss -tlnp | grep 3001`,
`echo "=== SECURITY: Port 4000 external test ===" && ss -tlnp | grep 4000`,
// Check upload directory permissions
`echo "=== UPLOAD DIR PERMS ===" && ls -la ${ROOT}/backend/public/api/uploads/ 2>/dev/null || echo "Upload dir does not exist yet (will be created on first upload)"`,
// Check proxy MongoDB connection
`echo "=== PROXY STATUS ===" && ${PM2} logs backone-proxy --lines 5 --nostream 2>&1 | grep -E "(MongoDB|Connected|Capacity|Scheduler)" | tail -10`,
// Verify no secrets in Next.js build output (sanity check)
`echo "=== SECURITY AUDIT: Check no API key in build ===" && grep -r "sk_db_live" ${ROOT}/.next/standalone/ 2>/dev/null | head -3 || echo "CLEAN: No Netify API key found in build output"`,
`echo "=== SECURITY AUDIT: Check no MongoDB URI in build ===" && grep -r "backone_user" ${ROOT}/.next/standalone/ 2>/dev/null | head -3 || echo "CLEAN: No MongoDB credentials in build output"`,
// Check frontend log for readiness
`echo "=== FRONTEND READY LOG ===" && ${PM2} logs backone-frontend --lines 5 --nostream 2>&1 | grep -E "(Ready|Error|Next.js)" | tail -10`,
// Check disk usage for uploads dir
`echo "=== DISK USAGE ===" && df -h /home/adminbackend/web/ 2>/dev/null | tail -2`,
// Final summary
`echo ""`,
`echo "╔════════════════════════════════════════════╗"`,
`echo "║ BackOne DPI Production — Service Status ║"`,
`echo "╠════════════════════════════════════════════╣"`,
`${PM2} list 2>/dev/null | grep -E "(online|error|stopped)" | awk '{printf "║ %-12s %-10s %-8s ║\\n", $2, $10, $8}'`,
`echo "╚════════════════════════════════════════════╝"`,
];
function runSshChecks(commands) {
return new Promise((resolve, reject) => {
const ssh = new SshClient();
ssh.on('ready', () => {
console.log('[SSH] Connected for verification!\n');
let index = 0;
function runNext() {
if (index >= commands.length) {
ssh.end();
return;
}
const cmd = commands[index++];
console.log(`\n$ ${cmd.substring(0, 120)}${cmd.length > 120 ? '...' : ''}`);
ssh.exec(cmd, (err, stream) => {
if (err) { console.error(`[ERROR] ${err.message}`); runNext(); return; }
stream.on('data', (d) => process.stdout.write(d.toString()));
stream.stderr.on('data', (d) => {
const t = d.toString();
if (!t.includes('npm warn') && !t.includes('npm notice')) process.stdout.write(t);
});
stream.on('close', runNext);
});
}
runNext();
ssh.on('end', resolve);
});
ssh.on('error', reject);
ssh.connect({ ...CONFIG, readyTimeout: 30000 });
});
}
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ BackOne DPI — Post-Deployment Verification ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// ── SSH internal checks ───────────────────────────────────────────────────
console.log('▶ Running internal server checks via SSH...');
await runSshChecks(SSH_COMMANDS);
// ── External HTTP checks (from this machine) ──────────────────────────────
console.log('\n\n▶ Running external HTTP checks from local machine...\n');
const checks = [
{ url: 'http://demoplace.my.id/', label: 'Domain root (redirect expected)' },
{ url: 'http://demoplace.my.id/login', label: 'Login page' },
{ url: 'http://demoplace.my.id/api/health', label: 'Backend health via domain' },
];
for (const { url, label } of checks) {
const result = await httpGet(url);
const status = result.status;
const icon = (status >= 200 && status < 400) ? '✅' : (status === 0 ? '⚠️' : '❌');
console.log(` ${icon} ${label}: HTTP ${status} ${result.error || ''}`);
if (result.body && status >= 200 && status < 300) {
console.log(` Body preview: ${result.body.substring(0, 100)}`);
}
}
console.log('\n══════════════════════════════════════════════════════════');
console.log(' Production deployment verification complete!');
console.log(' URL: https://demoplace.my.id');
console.log('══════════════════════════════════════════════════════════\n');
}
main().catch((err) => {
console.error('[FATAL]', err.message);
process.exit(1);
});