@lamalibre/create-portlama 1.0.5 → 1.0.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamalibre/create-portlama",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "One-command setup for secure reverse tunnels with a management dashboard",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Shared systemd unit and sudoers content generators.
3
+ * Used by both the full installer (panel.js) and the redeploy flow (redeploy.js).
4
+ */
5
+
6
+ /**
7
+ * Generate the portlama-panel systemd service unit content.
8
+ *
9
+ * @param {{ installDir: string, configDir: string }} ctx
10
+ * @returns {string}
11
+ */
12
+ export function generateServiceUnit(ctx) {
13
+ return `[Unit]
14
+ Description=Portlama Panel Server
15
+ After=network.target
16
+
17
+ [Service]
18
+ Type=simple
19
+ User=portlama
20
+ Group=portlama
21
+ WorkingDirectory=${ctx.installDir}/panel-server
22
+ ExecStart=/usr/bin/node src/index.js
23
+ Environment=NODE_ENV=production
24
+ Environment=CONFIG_FILE=${ctx.configDir}/panel.json
25
+ Restart=always
26
+ RestartSec=5
27
+ StandardOutput=journal
28
+ StandardError=journal
29
+ SyslogIdentifier=portlama-panel
30
+
31
+ # Security hardening
32
+ # Note: NoNewPrivileges is intentionally omitted — the panel needs sudo
33
+ # for provisioning (Chisel, Authelia, certbot, nginx, systemctl).
34
+ # Access is restricted via fine-grained sudoers rules in /etc/sudoers.d/portlama.
35
+ ProtectHome=true
36
+ ReadWritePaths=${ctx.configDir} /var/www/portlama
37
+ PrivateTmp=true
38
+
39
+ [Install]
40
+ WantedBy=multi-user.target
41
+ `;
42
+ }
43
+
44
+ /**
45
+ * Generate the portlama sudoers file content.
46
+ *
47
+ * @returns {string}
48
+ */
49
+ export function generateSudoersContent() {
50
+ const processUid = process.getuid?.() ?? 0;
51
+ const processGid = process.getgid?.() ?? 0;
52
+
53
+ return `# Portlama panel-server sudo rules
54
+ # Allows the portlama user to manage specific services and run specific commands
55
+
56
+ # --- systemctl: managed services ---
57
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start nginx
58
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop nginx
59
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
60
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx
61
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start chisel
62
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop chisel
63
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart chisel
64
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start authelia
65
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop authelia
66
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart authelia
67
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl reload authelia
68
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl daemon-reload
69
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl enable certbot.timer
70
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start certbot.timer
71
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl enable chisel
72
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl enable authelia
73
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start portlama-panel
74
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop portlama-panel
75
+ portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart portlama-panel
76
+
77
+ # --- nginx config test ---
78
+ portlama ALL=(root) NOPASSWD: /usr/sbin/nginx -t
79
+
80
+ # --- certbot: restrict certonly to --nginx only (prevents --manual-auth-hook) ---
81
+ portlama ALL=(root) NOPASSWD: /usr/bin/certbot certonly --nginx *
82
+ portlama ALL=(root) NOPASSWD: /usr/bin/certbot renew
83
+ portlama ALL=(root) NOPASSWD: /usr/bin/certbot renew --cert-name *
84
+ portlama ALL=(root) NOPASSWD: /usr/bin/certbot certificates
85
+
86
+ # --- openssl: only specific subcommands (no blanket wildcard) ---
87
+ portlama ALL=(root) NOPASSWD: /usr/bin/openssl x509 *
88
+ portlama ALL=(root) NOPASSWD: /usr/bin/openssl genrsa *
89
+ portlama ALL=(root) NOPASSWD: /usr/bin/openssl req *
90
+ portlama ALL=(root) NOPASSWD: /usr/bin/openssl pkcs12 *
91
+
92
+ # --- mv: restrict source to /tmp/ or known config paths ---
93
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /var/www/portlama/*
94
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/nginx/sites-available/*
95
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/systemd/system/*
96
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/portlama/pki/*
97
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /usr/local/bin/*
98
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/authelia/*
99
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /etc/portlama/pki/*.new /etc/portlama/pki/*
100
+ portlama ALL=(root) NOPASSWD: /usr/bin/mv /etc/nginx/sites-available/*.bak /etc/nginx/sites-available/*
101
+
102
+ # --- cp: only within known paths ---
103
+ portlama ALL=(root) NOPASSWD: /usr/bin/cp /etc/nginx/sites-available/* /etc/nginx/sites-available/*.bak
104
+ portlama ALL=(root) NOPASSWD: /usr/bin/cp /etc/portlama/pki/* /etc/portlama/pki/*.bak
105
+
106
+ # --- Authelia directories and file reads ---
107
+ portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /etc/authelia
108
+ portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /etc/authelia/*
109
+ portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /var/log/authelia
110
+ portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /var/log/authelia/*
111
+ portlama ALL=(root) NOPASSWD: /usr/bin/cat /etc/authelia/*
112
+
113
+ # --- Static site file operations under /var/www/portlama/ ---
114
+ portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /var/www/portlama/*
115
+ portlama ALL=(root) NOPASSWD: /usr/bin/chown -R www-data\\:www-data /var/www/portlama/*
116
+ portlama ALL=(root) NOPASSWD: /usr/bin/chown www-data\\:www-data /var/www/portlama/*
117
+ portlama ALL=(root) NOPASSWD: /usr/bin/chown ${processUid}\\:${processGid} /var/www/portlama/*
118
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod -R 755 /var/www/portlama/*
119
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /var/www/portlama/*
120
+ portlama ALL=(root) NOPASSWD: /usr/bin/rm -rf /var/www/portlama/*
121
+ portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /var/www/portlama/*
122
+ portlama ALL=(root) NOPASSWD: /usr/bin/find /var/www/portlama/*
123
+ portlama ALL=(root) NOPASSWD: /usr/bin/du -sb /var/www/portlama/*
124
+
125
+ # --- PKI file permissions ---
126
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod 600 /etc/portlama/pki/*
127
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/portlama/pki/*
128
+ portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/portlama/pki/*
129
+
130
+ # --- nginx vhost file permissions and cleanup ---
131
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/nginx/sites-available/*
132
+ portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/nginx/sites-available/*
133
+ portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/nginx/sites-enabled/*
134
+ portlama ALL=(root) NOPASSWD: /usr/bin/ln -sf /etc/nginx/sites-available/* /etc/nginx/sites-enabled/*
135
+
136
+ # --- systemd service file permissions ---
137
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/systemd/system/*
138
+
139
+ # --- chisel and authelia binary permissions ---
140
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod +x /usr/local/bin/chisel
141
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod +x /usr/local/bin/authelia
142
+
143
+ # --- authelia config permissions ---
144
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod 600 /etc/authelia/*
145
+ portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/authelia/*
146
+
147
+ # --- test file existence ---
148
+ portlama ALL=(root) NOPASSWD: /usr/bin/test -f /etc/nginx/sites-available/*
149
+ portlama ALL=(root) NOPASSWD: /usr/bin/test -r /etc/portlama/pki/*
150
+ `;
151
+ }
@@ -4,6 +4,7 @@ import { existsSync } from 'node:fs';
4
4
  import { setTimeout as sleep } from 'node:timers/promises';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { dirname, join } from 'node:path';
7
+ import { generateServiceUnit, generateSudoersContent } from '../lib/service-config.js';
7
8
 
8
9
  /**
9
10
  * Panel deployment subtasks: system user, directories, server + client deploy,
@@ -220,35 +221,7 @@ export function panelTasks(ctx, task) {
220
221
  {
221
222
  title: 'Writing systemd service unit',
222
223
  task: async (_ctx, subtask) => {
223
- const serviceUnit = `[Unit]
224
- Description=Portlama Panel Server
225
- After=network.target
226
-
227
- [Service]
228
- Type=simple
229
- User=portlama
230
- Group=portlama
231
- WorkingDirectory=${installDir}/panel-server
232
- ExecStart=/usr/bin/node src/index.js
233
- Environment=NODE_ENV=production
234
- Environment=CONFIG_FILE=${configDir}/panel.json
235
- Restart=always
236
- RestartSec=5
237
- StandardOutput=journal
238
- StandardError=journal
239
- SyslogIdentifier=portlama-panel
240
-
241
- # Security hardening
242
- # Note: NoNewPrivileges is intentionally omitted — the panel needs sudo
243
- # for provisioning (Chisel, Authelia, certbot, nginx, systemctl).
244
- # Access is restricted via fine-grained sudoers rules in /etc/sudoers.d/portlama.
245
- ProtectHome=true
246
- ReadWritePaths=${configDir} /var/www/portlama
247
- PrivateTmp=true
248
-
249
- [Install]
250
- WantedBy=multi-user.target
251
- `;
224
+ const serviceUnit = generateServiceUnit({ installDir, configDir });
252
225
  await writeFile(
253
226
  '/etc/systemd/system/portlama-panel.service',
254
227
  serviceUnit,
@@ -261,102 +234,7 @@ WantedBy=multi-user.target
261
234
  {
262
235
  title: 'Writing sudoers rules',
263
236
  task: async (_ctx, subtask) => {
264
- const processUid = process.getuid?.() ?? 0;
265
- const processGid = process.getgid?.() ?? 0;
266
-
267
- const sudoersContent = `# Portlama panel-server sudo rules
268
- # Allows the portlama user to manage specific services and run specific commands
269
-
270
- # --- systemctl: managed services ---
271
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start nginx
272
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop nginx
273
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
274
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx
275
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start chisel
276
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop chisel
277
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart chisel
278
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start authelia
279
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop authelia
280
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart authelia
281
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl reload authelia
282
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl daemon-reload
283
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl enable certbot.timer
284
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start certbot.timer
285
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl enable chisel
286
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl enable authelia
287
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl start portlama-panel
288
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl stop portlama-panel
289
- portlama ALL=(root) NOPASSWD: /usr/bin/systemctl restart portlama-panel
290
-
291
- # --- nginx config test ---
292
- portlama ALL=(root) NOPASSWD: /usr/sbin/nginx -t
293
-
294
- # --- certbot: restrict certonly to --nginx only (prevents --manual-auth-hook) ---
295
- portlama ALL=(root) NOPASSWD: /usr/bin/certbot certonly --nginx *
296
- portlama ALL=(root) NOPASSWD: /usr/bin/certbot renew
297
- portlama ALL=(root) NOPASSWD: /usr/bin/certbot renew --cert-name *
298
- portlama ALL=(root) NOPASSWD: /usr/bin/certbot certificates
299
-
300
- # --- openssl: only specific subcommands (no blanket wildcard) ---
301
- portlama ALL=(root) NOPASSWD: /usr/bin/openssl x509 *
302
- portlama ALL=(root) NOPASSWD: /usr/bin/openssl genrsa *
303
- portlama ALL=(root) NOPASSWD: /usr/bin/openssl req *
304
- portlama ALL=(root) NOPASSWD: /usr/bin/openssl pkcs12 *
305
-
306
- # --- mv: restrict source to /tmp/ or known config paths ---
307
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /var/www/portlama/*
308
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/nginx/sites-available/*
309
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/systemd/system/*
310
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/portlama/pki/*
311
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /usr/local/bin/*
312
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/authelia/*
313
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /etc/portlama/pki/*.new /etc/portlama/pki/*
314
- portlama ALL=(root) NOPASSWD: /usr/bin/mv /etc/nginx/sites-available/*.bak /etc/nginx/sites-available/*
315
-
316
- # --- cp: only within known paths ---
317
- portlama ALL=(root) NOPASSWD: /usr/bin/cp /etc/nginx/sites-available/* /etc/nginx/sites-available/*.bak
318
- portlama ALL=(root) NOPASSWD: /usr/bin/cp /etc/portlama/pki/* /etc/portlama/pki/*.bak
319
-
320
- # --- Static site file operations under /var/www/portlama/ ---
321
- portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /var/www/portlama/*
322
- portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /etc/authelia
323
- portlama ALL=(root) NOPASSWD: /usr/bin/mkdir -p /etc/authelia/*
324
- portlama ALL=(root) NOPASSWD: /usr/bin/chown -R www-data\\:www-data /var/www/portlama/*
325
- portlama ALL=(root) NOPASSWD: /usr/bin/chown www-data\\:www-data /var/www/portlama/*
326
- portlama ALL=(root) NOPASSWD: /usr/bin/chown ${processUid}\\:${processGid} /var/www/portlama/*
327
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod -R 755 /var/www/portlama/*
328
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /var/www/portlama/*
329
- portlama ALL=(root) NOPASSWD: /usr/bin/rm -rf /var/www/portlama/*
330
- portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /var/www/portlama/*
331
- portlama ALL=(root) NOPASSWD: /usr/bin/find /var/www/portlama/*
332
- portlama ALL=(root) NOPASSWD: /usr/bin/du -sb /var/www/portlama/*
333
-
334
- # --- PKI file permissions ---
335
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod 600 /etc/portlama/pki/*
336
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/portlama/pki/*
337
- portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/portlama/pki/*
338
-
339
- # --- nginx vhost file permissions and cleanup ---
340
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/nginx/sites-available/*
341
- portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/nginx/sites-available/*
342
- portlama ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/nginx/sites-enabled/*
343
- portlama ALL=(root) NOPASSWD: /usr/bin/ln -sf /etc/nginx/sites-available/* /etc/nginx/sites-enabled/*
344
-
345
- # --- systemd service file permissions ---
346
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/systemd/system/*
347
-
348
- # --- chisel and authelia binary permissions ---
349
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod +x /usr/local/bin/chisel
350
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod +x /usr/local/bin/authelia
351
-
352
- # --- authelia config permissions ---
353
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod 600 /etc/authelia/*
354
- portlama ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/authelia/*
355
-
356
- # --- test file existence ---
357
- portlama ALL=(root) NOPASSWD: /usr/bin/test -f /etc/nginx/sites-available/*
358
- portlama ALL=(root) NOPASSWD: /usr/bin/test -r /etc/portlama/pki/*
359
- `;
237
+ const sudoersContent = generateSudoersContent();
360
238
  const sudoersPath = '/etc/sudoers.d/portlama';
361
239
  await writeFile(sudoersPath, sudoersContent, { mode: 0o440 });
362
240
 
@@ -4,6 +4,7 @@ import { existsSync } from 'node:fs';
4
4
  import { setTimeout as sleep } from 'node:timers/promises';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { dirname, join } from 'node:path';
7
+ import { generateServiceUnit, generateSudoersContent } from '../lib/service-config.js';
7
8
 
8
9
  /**
9
10
  * Read the installed panel-server's package.json version, or null if not found.
@@ -179,6 +180,34 @@ export function redeployTasks(ctx, task) {
179
180
  },
180
181
  rendererOptions: { persistentOutput: true },
181
182
  },
183
+ {
184
+ title: 'Updating systemd unit and sudoers',
185
+ task: async (_ctx, subtask) => {
186
+ subtask.output = 'Writing systemd service unit...';
187
+ const serviceUnit = generateServiceUnit({ installDir, configDir });
188
+ await writeFile(
189
+ '/etc/systemd/system/portlama-panel.service',
190
+ serviceUnit,
191
+ );
192
+
193
+ subtask.output = 'Writing sudoers rules...';
194
+ const sudoersContent = generateSudoersContent();
195
+ const sudoersPath = '/etc/sudoers.d/portlama';
196
+ await writeFile(sudoersPath, sudoersContent, { mode: 0o440 });
197
+
198
+ try {
199
+ await execa('visudo', ['-c', '-f', sudoersPath]);
200
+ } catch (error) {
201
+ await rm(sudoersPath, { force: true });
202
+ throw new Error(
203
+ `Sudoers validation failed — file removed for safety.\n${error.stderr || error.message}`,
204
+ );
205
+ }
206
+
207
+ subtask.output = 'Systemd unit and sudoers updated';
208
+ },
209
+ rendererOptions: { persistentOutput: true },
210
+ },
182
211
  {
183
212
  title: 'Reloading systemd and restarting panel',
184
213
  task: async (_ctx, subtask) => {
@@ -99,7 +99,7 @@ export async function installAuthelia() {
99
99
  }
100
100
 
101
101
  const asset = releaseInfo.assets?.find(
102
- (a) => a.name.includes('linux-amd64') && a.name.endsWith('.tar.gz'),
102
+ (a) => a.name.includes('linux-amd64') && a.name.endsWith('.tar.gz') && !a.name.includes('musl'),
103
103
  );
104
104
 
105
105
  if (!asset) {