feat(production): sync 10/10 production codebase from demoplace.my.id
This commit is contained in:
commit
0156b84b0e
318 files changed
+56682
No files matched your search
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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
|
||||
});
|
||||
@@ -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); });
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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); });
|
||||
@@ -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();
|
||||
@@ -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); });
|
||||
@@ -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
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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); });
|
||||
@@ -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); });
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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); });
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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); });
|
||||
@@ -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); });
|
||||
@@ -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); });
|
||||
@@ -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));
|
||||
@@ -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); });
|
||||
@@ -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); });
|
||||
@@ -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();
|
||||
@@ -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 });
|
||||
@@ -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); });
|
||||
@@ -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); });
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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); });
|
||||
@@ -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));
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
Reference in new issue
Block a user