create-ppob-addon 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +329 -0
  2. package/package.json +7 -0
package/index.js ADDED
@@ -0,0 +1,329 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { Readable } = require("stream");
6
+ const { finished } = require("stream/promises");
7
+ const { execSync } = require("child_process");
8
+ const os = require("os");
9
+
10
+ // ============ šŸ”„ SERVER URL (HARDCODE) ============
11
+ // Ganti dengan URL server lo nanti
12
+ // Contoh: https://update.ppob-addon.com
13
+ // Atau: http://192.168.1.100:3000
14
+ const DEFAULT_SERVER_URL = "http://localhost:3000";
15
+
16
+ // Bisa di-override pake argumen atau env
17
+ const args = process.argv.slice(2);
18
+ const argServerUrl = args.find((arg) => arg.startsWith("--server="))?.split("=")[1];
19
+ const envServerUrl = process.env.SERVER_URL;
20
+ const SERVER_URL = argServerUrl || envServerUrl || DEFAULT_SERVER_URL;
21
+
22
+ // ============ DATA ============
23
+ const publicKeyData = `-----BEGIN PUBLIC KEY-----
24
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApXG04gRyR71EXKdUxjve
25
+ ol+/eCZOof0bl1rZMlAhO9UtYifqqnSYDyNCalwdQ9z3+pW2xOVbrEUfsIGR+Fqz
26
+ 0U+OTGphtai6uJmiVtErM+geuTp/sGUH3l9EfvZAMfnywK+OgXEITTWq3BQE8eRt
27
+ /Y0rTFPJtOzGWLFZ3kYu9ZdjPaXEI979Bz1l0tO5PLG/C84Q3GT90pcQPjLJJ5vd
28
+ xM17zm0zhJN6UG2tU+KXlVZAbvzffv/uFY5K1Mvp5ckaAwteAV/KREMzBZgReqyn
29
+ glXcB7GpaQh4QuXbona2UQqkNQhXU/ExXdI9Oo76KNvfEZIGAIrA3HEEsEOrpqfw
30
+ 3QIDAQAB
31
+ -----END PUBLIC KEY-----`;
32
+
33
+ // šŸ”„ .env akan di-generate dengan SERVER_URL yang dipake
34
+ const envData = `# ==================== SERVER ====================
35
+ PORT=5000
36
+ NODE_ENV=production
37
+
38
+ # ==================== UPDATE SERVER ====================
39
+ SERVER_URL=${SERVER_URL}
40
+ SERVER_WS_URL=${SERVER_URL.replace("http://", "ws://").replace("https://", "wss://")}
41
+
42
+ # ==================== REDIS ====================
43
+ REDIS_HOST=localhost
44
+ REDIS_PORT=6379
45
+ REDIS_PASSWORD=
46
+
47
+ # ==================== IP WHITELIST ====================
48
+ ALLOWED_IPS=127.0.0.1
49
+
50
+ # ==================== LOGGING ====================
51
+ LOG_LEVEL=info
52
+ LOG_FILE=./logs/app.log`;
53
+
54
+ const ecosystemConfigData = `module.exports = {
55
+ apps: [
56
+ {
57
+ name: "ppob_addon",
58
+ script: "current/index.js",
59
+ instances: 4,
60
+ exec_mode: "cluster",
61
+ watch: false,
62
+ ignore_watch: ["node_modules", "logs", ".git"],
63
+ error_file: "./logs/err.log",
64
+ out_file: "./logs/out.log",
65
+ log_file: "./logs/combined.log",
66
+ time: true,
67
+ max_memory_restart: "1G",
68
+ min_uptime: "10s",
69
+ max_restarts: 10,
70
+ restart_delay: 4000,
71
+ env: {
72
+ NODE_ENV: "production",
73
+ PORT: 5000,
74
+ },
75
+ },
76
+ ],
77
+ };`;
78
+
79
+ const packageJsonData = `{
80
+ "name": "ppob_addon",
81
+ "scripts": {
82
+ "pm2:start": "pm2 start ecosystem.config.js --env production",
83
+ "pm2:stop": "pm2 stop ppob_addon",
84
+ "pm2:delete": "pm2 delete ppob_addon",
85
+ "pm2:restart": "pm2 restart ppob_addon",
86
+ "pm2:reload": "pm2 reload ecosystem.config.js --update-env",
87
+ "pm2:reset": "pm2 delete ppob_addon && pm2 start ecosystem.config.js && pm2 save",
88
+ "pm2:logs": "pm2 logs ppob_addon",
89
+ "pm2:monit": "pm2 monit"
90
+ }
91
+ }`;
92
+
93
+ // ============ FUNCTIONS ============
94
+ async function download() {
95
+ console.log(`ā¬‡ļø Downloading from ${SERVER_URL}/api/update/download...`);
96
+ const response = await fetch(`${SERVER_URL}/api/update/download`);
97
+
98
+ if (!response.ok) {
99
+ throw new Error(`Failed to fetch: ${response.statusText}`);
100
+ }
101
+
102
+ const contentDisposition = response.headers.get("content-disposition");
103
+ if (!contentDisposition) {
104
+ throw new Error("No content-disposition header");
105
+ }
106
+
107
+ const match = contentDisposition.match(/filename="(.*?)"/);
108
+ if (!match) {
109
+ throw new Error("No filename in content-disposition");
110
+ }
111
+
112
+ const fileName = match[1];
113
+ const [, name, version] = fileName.match(/(\w+)-([0-9.]+)\.zip/);
114
+
115
+ const parentPath = path.join("releases");
116
+ const filePath = path.join(parentPath, fileName);
117
+
118
+ if (!fs.existsSync(parentPath)) {
119
+ fs.mkdirSync(parentPath, { recursive: true });
120
+ }
121
+
122
+ const destination = fs.createWriteStream(filePath);
123
+ const nodeReadable = Readable.fromWeb(response.body);
124
+ nodeReadable.pipe(destination);
125
+ await finished(destination);
126
+
127
+ console.log(`āœ… Download complete: ${fileName}`);
128
+
129
+ return {
130
+ success: true,
131
+ message: "Download completed successfully.",
132
+ data: {
133
+ name: fileName,
134
+ version,
135
+ filePath,
136
+ parentPath,
137
+ targetPath: filePath.replace(/\.zip$/, ""),
138
+ },
139
+ };
140
+ }
141
+
142
+ function unzipFile(zipPath, targetPath) {
143
+ const isWindows = os.platform() === "win32";
144
+ console.log(`šŸ“¦ Extracting ${path.basename(zipPath)}...`);
145
+
146
+ if (isWindows) {
147
+ try {
148
+ execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${targetPath}' -Force"`);
149
+ } catch {
150
+ console.log("āš ļø PowerShell failed, trying 7zip...");
151
+ execSync(`7z x ${zipPath} -o${targetPath} -y`);
152
+ }
153
+ } else {
154
+ execSync(`unzip -o ${zipPath} -d ${targetPath}`);
155
+ }
156
+
157
+ console.log(`āœ… Extraction complete: ${targetPath}`);
158
+ }
159
+
160
+ function createSymlink(targetPath, linkPath) {
161
+ console.log(`šŸ”— Creating symlink: ${linkPath} → ${targetPath}`);
162
+
163
+ if (fs.existsSync(linkPath)) {
164
+ fs.unlinkSync(linkPath);
165
+ }
166
+
167
+ fs.symlinkSync(targetPath, linkPath, "junction");
168
+ console.log(`āœ… Symlink created`);
169
+ }
170
+
171
+ function checkPrerequisites() {
172
+ console.log("šŸ” Checking prerequisites...");
173
+
174
+ // 1. Cek Node.js
175
+ try {
176
+ const nodeVersion = execSync("node --version", { encoding: "utf8" }).trim();
177
+ const requiredVersion = "v24.11.1";
178
+
179
+ if (nodeVersion !== requiredVersion) {
180
+ console.error(`āŒ Node.js version mismatch!`);
181
+ console.error(` Required: ${requiredVersion}`);
182
+ console.error(` Current: ${nodeVersion}`);
183
+ console.error(`\nšŸ“„ Download Node.js ${requiredVersion} from:`);
184
+ console.error(` https://nodejs.org/dist/v24.11.1/`);
185
+ process.exit(1);
186
+ }
187
+ console.log(`āœ… Node.js ${nodeVersion}`);
188
+ } catch {
189
+ console.error("āŒ Node.js is not installed!");
190
+ console.error("šŸ“„ Download from: https://nodejs.org/dist/v24.11.1/");
191
+ process.exit(1);
192
+ }
193
+
194
+ // 2. Cek PM2
195
+ try {
196
+ const pm2Version = execSync("pm2 --version", { encoding: "utf8" }).trim();
197
+ console.log(`āœ… PM2 ${pm2Version}`);
198
+ } catch {
199
+ console.log("āš ļø PM2 not found, installing globally...");
200
+ execSync("npm install -g pm2", { stdio: "inherit" });
201
+ }
202
+
203
+ // 3. Cek unzip (Linux/Mac)
204
+ if (os.platform() !== "win32") {
205
+ try {
206
+ execSync("which unzip", { stdio: "ignore" });
207
+ console.log("āœ… unzip command found");
208
+ } catch {
209
+ console.error("āŒ unzip command not found!");
210
+ console.log("Install with: sudo apt-get install unzip");
211
+ process.exit(1);
212
+ }
213
+ }
214
+
215
+ // šŸ”„ 4. Cek Redis/Memurai
216
+ console.log("šŸ” Checking Redis/Memurai...");
217
+ const isWindows = os.platform() === "win32";
218
+ let redisOk = false;
219
+
220
+ if (isWindows) {
221
+ try {
222
+ const result = execSync("sc query Memurai", { encoding: "utf8" });
223
+ if (result.includes("RUNNING")) {
224
+ console.log("āœ… Memurai is running");
225
+ redisOk = true;
226
+ } else {
227
+ console.log("āš ļø Memurai installed but not running");
228
+ console.log(" Run: net start Memurai");
229
+ }
230
+ } catch {
231
+ console.log("āš ļø Memurai not found, checking port 6379...");
232
+ // Cek port 6379 (mungkin Redis jalan lewat Docker)
233
+ try {
234
+ execSync("netstat -ano | findstr :6379", { encoding: "utf8", stdio: "ignore" });
235
+ console.log("āœ… Port 6379 is open (Redis is running)");
236
+ redisOk = true;
237
+ } catch {
238
+ console.log("āŒ Redis/Memurai not found!");
239
+ console.log("šŸ“„ Install Memurai: https://www.memurai.com/get-memurai");
240
+ console.log(" Or run Redis via Docker:");
241
+ console.log(" docker run -d --name redis -p 6379:6379 redis");
242
+ }
243
+ }
244
+ } else {
245
+ try {
246
+ const result = execSync("redis-cli ping", { encoding: "utf8" });
247
+ if (result.includes("PONG")) {
248
+ console.log("āœ… Redis is running");
249
+ redisOk = true;
250
+ }
251
+ } catch {
252
+ try {
253
+ execSync("netstat -tlnp | grep :6379", { encoding: "utf8", stdio: "ignore" });
254
+ console.log("āœ… Port 6379 is open (Redis is running)");
255
+ redisOk = true;
256
+ } catch {
257
+ console.log("āŒ Redis not found!");
258
+ console.log("šŸ“„ Install: sudo apt-get install redis-server");
259
+ console.log(" Or run via Docker: docker run -d --name redis -p 6379:6379 redis");
260
+ }
261
+ }
262
+ }
263
+
264
+ // 5. Kasih warning kalo Redis nggak jalan
265
+ if (!redisOk) {
266
+ console.log("\nāš ļø WARNING: Redis/Memurai is not running!");
267
+ console.log(" The app will still run but may have issues with:");
268
+ console.log(" - Session management");
269
+ console.log(" - Rate limiting");
270
+ console.log(" - Caching");
271
+ console.log("\n Please install and start Redis/Memurai before running the app.\n");
272
+ }
273
+
274
+ console.log("āœ… All prerequisites satisfied");
275
+ }
276
+
277
+ async function install() {
278
+ console.log("\nšŸš€ PPOB_ADDON Installer\n");
279
+ console.log(`šŸ“” Server URL: ${SERVER_URL}\n`);
280
+
281
+ checkPrerequisites();
282
+
283
+ console.log("\nšŸ“ Generating configuration files...");
284
+ const resources = [
285
+ ["keys/public.key", publicKeyData],
286
+ [".env", envData],
287
+ ["ecosystem.config.js", ecosystemConfigData],
288
+ ["package.json", packageJsonData],
289
+ ];
290
+
291
+ for (const [file, data] of resources) {
292
+ if (fs.existsSync(file)) {
293
+ console.log(`ā­ļø Skipping ${file} (already exists)`);
294
+ continue;
295
+ }
296
+
297
+ const dir = path.dirname(file);
298
+ if (!fs.existsSync(dir)) {
299
+ fs.mkdirSync(dir, { recursive: true });
300
+ }
301
+ fs.writeFileSync(file, data);
302
+ console.log(`āœ… Created ${file}`);
303
+ }
304
+
305
+ console.log("\nā¬‡ļø Downloading latest version...");
306
+ const downloadResult = await download();
307
+ if (!downloadResult.success) {
308
+ console.error("āŒ Download failed:", downloadResult);
309
+ process.exit(1);
310
+ }
311
+
312
+ const { filePath, targetPath } = downloadResult.data;
313
+ unzipFile(filePath, targetPath);
314
+ createSymlink(targetPath, path.join("current"));
315
+
316
+
317
+ console.log("\nšŸŽ‰ Installation complete!\n");
318
+ console.log("šŸ“ Next steps:");
319
+ console.log(" 1. Edit .env if needed");
320
+ console.log(" 2. Run: npm run pm2:start");
321
+ console.log(" 3. Check: npm run pm2:status");
322
+ console.log("\nšŸ’” All dependencies are bundled, no need to run npm install!");
323
+
324
+ }
325
+
326
+ install().catch((error) => {
327
+ console.error("āŒ Installation failed:", error.message);
328
+ process.exit(1);
329
+ });
package/package.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "create-ppob-addon",
3
+ "bin": {
4
+ "create-ppob-addon": "index.js"
5
+ },
6
+ "version": "1.0.0"
7
+ }