diff --git a/CHANGELOG.md b/CHANGELOG.md index 37fd8a8..1084887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/). +## [0.6.7] — 2026-11-06 — "isolir-cloud" + +### Ditambahkan +- Tab **Cloud (Sederhana)** pada modal Script Isolir (Manajemen Router): generate script RouterOS siap tempel untuk billing yang berjalan di cloud. + - Input domain/IP publik billing, port redirector, dan profil PPPoE isolir. + - Berisi: address-list BILLING (semua hasil resolve A), hook profil PPPoE, redirect DNS, NAT HTTP ke redirector, serta filter allow/reject/drop. + - Deteksi Cloudflare/CDN + peringatan bila port redirector tidak ikut diproxy. +- Endpoint `GET /admin/api/isolir-cloud-script`. + ## [0.6.6] — 2026-11-06 — "qr-custom-bulk" ### Ditambahkan diff --git a/README.md b/README.md index 1afeba6..ad6edcb 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@
-
+
diff --git a/package.json b/package.json
index e27f087..bae36e6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "open-isp",
- "version": "0.6.6",
+ "version": "0.6.7",
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
"main": "app-customer.js",
"scripts": {
diff --git a/routes/adminPortal.js b/routes/adminPortal.js
index 64976d1..5dc264c 100644
--- a/routes/adminPortal.js
+++ b/routes/adminPortal.js
@@ -7673,9 +7673,12 @@ router.post('/whatsapp/repair-keys', requireAdminSession, async (req, res) => {
// ─── ROUTERS (MULTI-ROUTER) ──────────────────────────────────────────────────
router.get('/routers', requireAdminSession, requireSidebarMenuAccess('mikrotik'), (req, res) => {
+ const _settings = getSettings();
+ const defaultPublicBase = String(_settings.public_base_url || _settings.app_url || ((req.headers['x-forwarded-proto'] || req.protocol) + '://' + req.get('host'))).replace(/\/+$/, '');
res.render('admin/routers', {
title: 'Manajemen Router', company: company(), activePage: 'mikrotik',
- routers: mikrotikService.getAllRouters(), msg: flashMsg(req)
+ routers: mikrotikService.getAllRouters(), msg: flashMsg(req),
+ defaultPublicBase
});
});
@@ -7987,6 +7990,19 @@ router.post('/api/routers/:id/setup-firewall', requireAdmin, async (req, res) =>
}
});
+router.get('/api/isolir-cloud-script', requireAdmin, async (req, res) => {
+ try {
+ const data = await mikrotikService.generateIsolirCloudScript({
+ host: req.query.host,
+ port: req.query.port,
+ profile: req.query.profile
+ });
+ res.json({ success: true, ...data });
+ } catch (e) {
+ res.json({ success: false, error: e.message });
+ }
+});
+
router.get('/api/isolir-portal-script', requireAdmin, async (req, res) => {
try {
const data = await mikrotikService.generateIsolirPortalScript();
diff --git a/services/mikrotikService.js b/services/mikrotikService.js
index a4f76a8..1a79efd 100644
--- a/services/mikrotikService.js
+++ b/services/mikrotikService.js
@@ -1632,6 +1632,112 @@ async function generateIsolirPortalScript() {
};
}
+/**
+ * Script isolir sederhana untuk billing yang berjalan di CLOUD.
+ * Asumsi: redirector billing (port isolir_redirect_port) terbuka dari publik.
+ */
+async function generateIsolirCloudScript(opts = {}) {
+ const settings = getSettingsWithCache() || {};
+ const rawHost = String(opts.host || settings.public_base_url || settings.app_url || '').trim();
+ if (!rawHost) throw new Error('Isi domain/IP publik billing terlebih dahulu.');
+
+ let url = null;
+ try {
+ url = new URL(/^https?:\/\//i.test(rawHost) ? rawHost : ('https://' + rawHost));
+ } catch (e) {
+ throw new Error('Domain/IP billing tidak valid.');
+ }
+ const hostname = url.hostname;
+ const baseUrl = url.origin;
+ let billingIps = [];
+ if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname)) {
+ billingIps = [hostname];
+ } else {
+ try {
+ const list = await dns.promises.resolve4(hostname);
+ billingIps = (list || []).filter(Boolean);
+ } catch (e) {}
+ if (!billingIps.length) {
+ try { billingIps = [await resolveIpv4(hostname)]; } catch (e) { billingIps = []; }
+ }
+ }
+ if (!billingIps.length) throw new Error('IP publik billing tidak bisa di-resolve. Masukkan IP origin langsung.');
+ const billingIp = billingIps[0];
+ const cfRanges = ['104.16.', '104.17.', '104.18.', '104.19.', '104.20.', '104.21.', '104.22.', '104.23.', '104.24.', '104.25.', '104.26.', '104.27.', '172.64.', '172.65.', '172.66.', '172.67.', '162.158.', '173.245.', '103.21.', '103.22.', '103.31.', '141.101.', '108.162.', '190.93.', '188.114.', '197.234.', '198.41.', '131.0.72.'];
+ const isCdn = billingIps.some(ip => cfRanges.some(p => ip.startsWith(p)));
+ const redirectPort = Math.max(1, Math.min(65535, parseInt(opts.port, 10) || parseInt(settings.isolir_redirect_port, 10) || 8099));
+ const profile = String(opts.profile || 'isolir').trim() || 'isolir';
+ const isolirUrl = String(settings.isolir_portal_url || (baseUrl + '/isolated')).trim();
+
+ const onUp = `/ip firewall address-list remove [find list=LIST_ISOLIR address=$\\"remote-address\\"]; /ip firewall address-list add list=LIST_ISOLIR address=$\\"remote-address\\" comment=$user timeout=23h`;
+ const onDown = `/ip firewall address-list remove [find list=LIST_ISOLIR address=$\\"remote-address\\"]`;
+
+ const lines = [
+ '# ============================================================',
+ '# ISOLIR PAGE — BILLING CLOUD (script sederhana)',
+ '# Tempel di Terminal RouterOS. Ubah variabel di bawah bila perlu.',
+ '# ============================================================',
+ ...(isCdn ? [
+ '# PERINGATAN: ' + hostname + ' memakai Cloudflare/CDN.',
+ '# Port ' + redirectPort + ' biasanya TIDAK ikut diproxy Cloudflare.',
+ '# Untuk NAT redirector, gunakan IP ORIGIN (DNS-only) atau pastikan',
+ '# port ' + redirectPort + ' terbuka langsung di server origin.',
+ '# ============================================================',
+ ''
+ ] : []),
+ ':local billingIp "' + billingIp + '"',
+ ':local isolirPort ' + redirectPort,
+ ':local isolirProfile "' + profile + '"',
+ '',
+ '# --- Bersihkan rule lama buatan script ini ---',
+ '/ip firewall nat remove [find comment~"ISOLIR_CLOUD_"]',
+ '/ip firewall filter remove [find comment~"ISOLIR_CLOUD_"]',
+ '/ip firewall address-list remove [find list=BILLING comment="BILLING_ISOLIR"]',
+ '',
+ '# --- Izinkan billing (semua IP hasil resolve) ---',
+ ...billingIps.map(function (ip) { return '/ip firewall address-list add list=BILLING address=' + ip + ' comment="BILLING_ISOLIR"'; }),
+ '',
+ '# --- Hook profil PPPoE isolir ---',
+ '/ppp profile set [find name=$isolirProfile] on-up="' + onUp + '" on-down="' + onDown + '"',
+ '',
+ '# --- DNS isolir diarahkan ke router ---',
+ '/ip firewall nat add chain=dstnat protocol=udp dst-port=53 src-address-list=LIST_ISOLIR action=redirect to-ports=53 comment="ISOLIR_CLOUD_DNS_UDP"',
+ '/ip firewall nat add chain=dstnat protocol=tcp dst-port=53 src-address-list=LIST_ISOLIR action=redirect to-ports=53 comment="ISOLIR_CLOUD_DNS_TCP"',
+ '',
+ '# --- HTTP pelanggan isolir -> redirector cloud ---',
+ '/ip firewall nat add chain=dstnat protocol=tcp dst-port=80 src-address-list=LIST_ISOLIR dst-address-list=!BILLING action=dst-nat to-addresses=$billingIp to-ports=$isolirPort comment="ISOLIR_CLOUD_HTTP"',
+ '',
+ '# --- Filter ---',
+ '/ip firewall filter add chain=forward src-address-list=LIST_ISOLIR protocol=udp dst-port=53 action=accept comment="ISOLIR_CLOUD_DNS"',
+ '/ip firewall filter add chain=forward src-address-list=LIST_ISOLIR protocol=tcp dst-port=53 action=accept comment="ISOLIR_CLOUD_DNS_TCP"',
+ '/ip firewall filter add chain=forward src-address-list=LIST_ISOLIR dst-address-list=BILLING action=accept comment="ISOLIR_CLOUD_ALLOW_BILLING"',
+ '/ip firewall filter add chain=forward src-address-list=LIST_ISOLIR protocol=tcp dst-port=443 action=reject comment="ISOLIR_CLOUD_REJECT_HTTPS"',
+ '/ip firewall filter add chain=forward src-address-list=LIST_ISOLIR action=drop comment="ISOLIR_CLOUD_DROP"',
+ '',
+ '# ============================================================',
+ '# CATATAN BILLING CLOUD',
+ '# 1. Redirector billing harus bisa diakses: http://' + billingIp + ':' + redirectPort,
+ '# (buka port ' + redirectPort + ' di firewall/security group cloud).',
+ '# 2. Di billing set ISOLIR_REDIRECT_URL=' + isolirUrl,
+ '# atau setting isolir_portal_url.',
+ '# 3. Halaman isolir: ' + isolirUrl,
+ '# 4. Jika pakai Cloudflare/CDN, pastikan IP edge yang dipakai ada di',
+ '# address-list BILLING, atau gunakan IP origin langsung.',
+ '# ============================================================',
+ ];
+ return {
+ script: lines.join('\n'),
+ billingHost: hostname,
+ billingIp,
+ billingIps,
+ isCdn,
+ redirectPort,
+ profile,
+ isolirUrl,
+ warning: isCdn ? ('Domain memakai Cloudflare/CDN. NAT redirector ke ' + billingIp + ':' + redirectPort + ' biasanya gagal — gunakan IP origin (DNS-only) atau buka port redirector di origin.') : ''
+ };
+}
+
const ISOLIR_ADDR_LIST = 'LIST_ISOLIR';
/** Nama profil PPPoE isolir yang dipakai pelanggan di router ini (distinct dari DB). */
@@ -2220,6 +2326,7 @@ module.exports = {
setupIsolirFirewall,
ensurePppProfileIsolirAddressListHook,
generateIsolirPortalScript,
+ generateIsolirCloudScript,
manageStaticIp,
removeStaticIp
};
diff --git a/version.txt b/version.txt
index bf21f52..8b707c6 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.6.6
\ No newline at end of file
+0.6.7
\ No newline at end of file
diff --git a/views/admin/routers.ejs b/views/admin/routers.ejs
index 90fbade..af1e808 100644
--- a/views/admin/routers.ejs
+++ b/views/admin/routers.ejs
@@ -527,6 +527,9 @@
+
@@ -554,6 +557,23 @@