Con quale utente si avvia pm2 e quali permessi utilizza?

Risoluzione dei Problemi di Permessi su Cartelle e Processi PM2 in Linux

Quando si utilizzano applicazioni Node.js gestite da PM2, su macchine in cui accedono più utenti, è possibile imbattersi, come è capitato a me, in problemi di permessi su file e cartelle. In particolare, può accadere che un processo avviato da un utente non riesca a scrivere su una directory, nonostante l’utente appartenga apparentemente al gruppo corretto.

A volte aggiungi un utente a un gruppo, imposti i permessi corretti sulle cartelle… ma l’app continua a dare EACCES: permission denied. Perché? Perché PM2 (o il processo che lancia) non ha aggiornato i gruppi effettivi.

In questo articolo vediamo come diagnosticare questi problemi in modo chiaro usando una semplice app Node.js e quando è necessario riavviare PM2 o l’intero sistema.

Il Cuore del Problema

Aggiungi l’utente a un gruppo ➡️ Ma non basta

  • Il kernel non aggiorna i gruppi effettivi di un processo già in esecuzione.
  • PM2 eredita i gruppi al momento del lancio, e li mantiene finché il processo non viene riavviato.
  • A volte, solo un riavvio della macchina garantisce la ripartenza pulita di tutti i processi, specie se PM2 parte come servizio systemd.

L’Applicazione di Test dei Permessi

Creiamo una piccola app Node.js che:

  • Mostra l’utente attuale e i suoi gruppi
  • Controlla i permessi su una directory
  • Prova a scrivere un file di test

Sorgente applicazione di test

const os = require('os');
const fs = require('fs');
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const targetFolder = '/percorso/della/cartella'; // <-- personalizza qui!
const testFile = `${targetFolder}/testfile.txt`;

(async () => {
console.log('=============================');
console.log('🧑‍💻 User & Group Info');
console.log('=============================');
console.log(`User: ${os.userInfo().username}`);
console.log(`UID : ${process.getuid()}`);
console.log(`GID : ${process.getgid()}`);

const { stdout: groups } = await exec('id');
console.log(`Groups: ${groups.trim()}`);

console.log('\n=============================');
console.log('📁 Folder Permissions');
console.log('=============================');
const folderStats = fs.statSync(targetFolder);
console.log(`Path : ${targetFolder}`);
console.log(`Owner UID : ${folderStats.uid}`);
console.log(`Owner GID : ${folderStats.gid}`);
console.log(`Permissions : ${folderStats.mode.toString(8)}`);

console.log('\n=============================');
console.log('📝 Test Write Access');
console.log('=============================');
try {
fs.writeFileSync(testFile, `Test scritto da ${os.userInfo().username} - ${new Date().toISOString()}\n`, { flag: 'a' });
console.log(`✅ Scrittura su ${testFile} OK`);
} catch (err) {
console.error(`❌ Non riesco a scrivere su ${testFile}`);
console.error(err.message);
}

console.log('\nFINE TEST');
})();

Salva questo test file (ad esempio index.js) su una cartella dalla quale puoi lanciare i seguenti comandi:

pm2 start index.js --name testpm2perm
pm2 logs testpm2perm

Io l’ho chiamata testpm2perm, tu fai come ti pare.

Con questo software riuscite a capire se ci sono dei path che vi sono preclusi e, soprattutto, con quale utente gira pm2.

Pertanto, potreste avere dei permessi insufficienti per manipolare i files di una cartella se lanciate il programma con pm2.

Una volta reimpostati i permessi e i gruppi degli utenti, in particolare all’utente che è dietro pm2, tali da consentirgli la manipolazione dei file, potreste essere costretti a riavviare il sistema, prima di riuscire a vedere il bug risolto e il vostro software funzionare.

Quando è Necessario il Reboot?

  • PM2 viene gestito da systemd e i processi non vengono aggiornati correttamente
  • Il gruppo è stato aggiunto, ma il processo parte troppo presto durante il boot
  • I processi sono zombie di vecchie sessioni non aggiornate

Lo script esposto in questo articolo è stato scritto con intelligenza artificiale e mi ha aiutato a risolvere una problematica reale.
Questo articolo è solo una scusa per appuntarmi questa soluzione e, perché no, renderla disponibile agli altri.

Come loggare tutte le richieste ricevute da NodeJS/Express

Una pila di cartelle e documenti su una scrivania, con una persona in abito e occhiali da nerd

Se hai bisogno di loggare tutte le richieste ricevute dal tuo software che gira su NodeJS/Express, sappi che esiste un moduletto perfetto per l’eventualità.

Avrai bisogno di Morgan ed FS e in particolare dovrai avere:

  • modulo morgan: Questo modulo ci permette di loggare le richieste HTTP.
  • modulo fs: Serve per gestire il file system e scrivere i log su file.
  • Middleware personalizzato: Questo middleware raccoglie le informazioni richieste (URL referrer, metodo, rotta chiamata, ecc.).
  • Un file di testo: Utilizzeremo fs per scrivere i log in append.

Avrai bisogno di Morgan ed FS, se non l’hai fatto, installali:

npm install morgan fs

Dopo aver installato morgan e fs, puoi semplicemente aggiungere codice di questo tipo:

const express = require('express');
const fs = require('fs');
const path = require('path');
const morgan = require('morgan');

const app = express();

// Crea un flusso di scrittura per salvare i log in un file di testo
const logStream = fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' });

// Middleware per loggare le richieste HTTP
app.use(morgan((tokens, req, res) => {
    return [
        new Date().toISOString(),
        tokens.method(req, res),    // Metodo HTTP (GET, POST, etc.)
        tokens.url(req, res),       // URL della rotta
        tokens['referrer'](req, res),  // Referrer
        tokens.status(req, res),    // Status della risposta
        tokens['response-time'](req, res), 'ms'  // Tempo di risposta
    ].join(' ');
}, { stream: logStream }));

Cosa fa il codice:

  • Morgan: Usato per loggare le richieste. Il log viene personalizzato per includere il metodo, la rotta chiamata, il referrer e altre informazioni.
  • fs.createWriteStream: Crea un file chiamato access.log dove vengono salvati i log.
  • Log personalizzato: Il middleware logga le informazioni come data, metodo HTTP, URL della richiesta, referrer e tempo di risposta.

Questo è un sistema semplice ed efficace per tenere traccia di tutte le richieste fatte al server. Puoi adattarlo in base a eventuali esigenze specifiche.

Ciao 🙂

Come stampare tutte le rotte utilizzate da NodeJS e Express?

Goniometro

Ecco un semplice script che consente, opportunamente integrato con il proprio progetto, di stampare tutte le rotte esposte da un software con NodeJS ed Express.

Creare un file chiamato printRoutes.js. Potete metterlo dove vi pare, io l’ho messo in una cartella tools.

Tale file deve contenere questo codice sorgente:

function printRoutes(app) {
    app._router.stack.forEach((middleware) => {
        if (middleware.route) {
            // Route middleware
            const methods = Object.keys(middleware.route.methods).join(', ').toUpperCase();
            console.log(`${methods} ${middleware.route.path}`);
        } else if (middleware.name === 'router') {
            // Router middleware
            middleware.handle.stack.forEach((handler) => {
                const methods = Object.keys(handler.route.methods).join(', ').toUpperCase();
                console.log(`${methods} ${middleware.regexp}${handler.route.path}`);
            });
        }
    });
}

function split(thing) {
    if (typeof thing === 'string') {
        return thing;
    } else if (thing.fast_slash) {
        return '';
    } else {
        const match = thing
        .toString()
        .replace('\\/?', '')
        .replace('(?=\\/|$)', '$')
        .match(/^\/\^\\(.*)\\\$\//);
        return match ? match[1].replace(/\\\//g, '/') : '<complex: ' + thing.toString() + '>';
    }
}

export {printRoutes}

Successivamente, dovrete richiamare tale funzionalità nell’ambito del listening della vostra applicazione.

Normalmente avrete un file denominato server.js o index.js nella root del progetto e richiamare la funzione, prima di tutto importando il modulo per effettuare la stampa delle rotte esposte dal software:

import {printRoutes} from './tools/printRoutes.js'

Quindi, richiamare il printRoutes, nel punto giusto (di solito nell’app.listen), ad esempio:

import {printRoutes} from './tools/printRoutes.js'

...

app.listen(process.env.SRV_PORT, process.env.SRV_IP, () => {

...

// Itera attraverso le rotte e stampa ogni rotta
printRoutes(app);

});

Salvate, rilanciate il server e guardate la console, troverete tutte le rotte che il vostro progetto espone.