Tag: linux

  • WireGuard su Ubuntu: come vedere IP assegnato e stampare la configurazione (wg showconf, ip a)

    WireGuard su Ubuntu: come vedere IP assegnato e stampare la configurazione (wg showconf, ip a)

    Se hai seguito la guida per gestire WireGuard su Ubuntu con un’icona in tray tramite Python, probabilmente a questo punto ti starai chiedendo:

    • Che IP mi ha assegnato la VPN?
    • Come faccio a vedere la configurazione attiva di WireGuard?
    • Come stampo il file .conf che sto usando?

    In questo articolo vediamo i comandi più utili (e più rapidi) per controllare tutto dal terminale, senza impazzire.

    Tutti i comandi qui sotto funzionano su Ubuntu e derivati (Debian, Mint, ecc.).
    In alcuni casi è richiesto sudo.

    Come vedere l’IP che ti ha assegnato WireGuard

    Quando WireGuard è attivo, crea un’interfaccia di rete virtuale (di solito chiamata wg0).

    Per vedere tutte le interfacce e trovare quella WireGuard:

    ip a

    Cerca una sezione simile a questa:

    3: wg0: <POINTOPOINT,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default
        inet 10.7.0.2/24 scope global wg0
           valid_lft forever preferred_lft forever

    In questo esempio, l’IP assegnato dalla VPN è:

    10.7.0.2

    Stampare solo l’IP della VPN (senza “rumore”)

    Se vuoi vedere direttamente l’IP IPv4 associato a wg0:

    ip -4 addr show dev wg0

    Se invece vuoi solo l’indirizzo pulito (utile anche per script):

    ip -4 addr show dev wg0 | grep -oP 'inet \K[\d.]+'

    Esempio output:

    10.7.0.2

    Come stampare la configurazione WireGuard

    Ci sono due “tipi” di configurazione che puoi voler vedere:

    1. Il file di configurazione (wg0.conf)
    2. La configurazione attiva (runtime), cioè quella realmente caricata e in uso

    Vediamole entrambe.

    1) Stampare il file .conf (quello che hai configurato)

    Di solito i file WireGuard si trovano in:

    /etc/wireguard/

    E il file classico è:

    /etc/wireguard/wg0.conf

    Per stamparlo:

    sudo cat /etc/wireguard/wg0.conf

    Se vuoi una stampa più leggibile con i numeri di riga:

    sudo nl -ba /etc/wireguard/wg0.conf

    Questo è comodo quando devi copiare/incollare righe specifiche o confrontare config.

    2) Stampare la configurazione attiva (quella realmente in uso)

    Questa è la parte più utile quando vuoi fare debug o capire se la VPN è davvero connessa.

    Config attiva in formato “conf”

    sudo wg showconf wg0

    Questo comando ti stampa una versione “ricostruita” della configurazione attiva dell’interfaccia.

    Stato completo dell’interfaccia (debug)

    sudo wg show wg0

    Qui vedrai:

    • public key
    • endpoint (IP:porta del server)
    • allowed IPs
    • handshake recente
    • transfer (rx/tx)

    Esempio tipico:

    • se handshake è recente, sei connesso
    • se transfer aumenta, il traffico sta passando

    Vedere tutte le interfacce WireGuard attive

    Se non sei sicuro del nome (wg0, wg1, ecc.):

    sudo wg show

    Nota importante: IP VPN vs IP pubblico

    Qui spesso ci si confonde, quindi vale la pena chiarire:

    IP della VPN (interno)

    Quello che vedi con:

    ip -4 addr show dev wg0

    è un IP “interno” della rete VPN (es. 10.x.x.x, 192.168.x.x, ecc.)

    IP pubblico (quello che vede internet)

    Se la VPN instrada il traffico verso internet, allora il tuo IP pubblico cambia.

    Puoi verificarlo così:

    curl ifconfig.me

    Oppure:

    curl ipinfo.io/ip

    Se vedi un IP diverso dal tuo normale, allora stai uscendo su internet tramite VPN.

    Mini-checklist veloce (comandi utili)

    Vedere l’IP assegnato alla VPN:

    ip -4 addr show dev wg0

    Stampare solo l’IP:

    ip -4 addr show dev wg0 | grep -oP 'inet \K[\d.]+'

    Stampare il file di configurazione:

    sudo cat /etc/wireguard/wg0.conf

    Config attiva (in formato conf):

    sudo wg showconf wg0

    Stato completo:

    sudo wg show wg0

    Vedere l’IP pubblico:

    curl ifconfig.me

    Con questo abbiamo chiuso

    Dopo aver configurato WireGuard e averlo reso comodo da gestire tramite tray (come nella guida precedente), questi comandi ti permettono di:

    • verificare subito se la VPN è attiva
    • controllare che IP ti è stato assegnato
    • stampare la configurazione file e runtime
    • capire se stai navigando con un IP pubblico diverso

    Se vuoi, nel prossimo articolo possiamo aggiungere anche:

    • un pulsante nella tray app per mostrare l’IP attuale
    • una notifica desktop con IP e handshake
    • controllo “VPN attiva/non attiva” più affidabile

    Buon tunnel

  • WireGuard su Ubuntu 22: icona tray con Python (stato + start/stop)

    WireGuard su Ubuntu 22: icona tray con Python (stato + start/stop)

    Quando ho dovuto scegliere come collegarmi in VPN ad un servizio per faccende di lavoro, mi sono trovato con il mio pinguino a dover scegliere come farlo, dopo qualche prova e qualche esperimento le mie scelte sono converg… convers… finite su Wireguard, che sostanzialmente era quello che rompeva meno le scatole.

    HINT!

    Chiunque usi una VPN con WireGuard sa che il comando magico è sempre quello:

    sudo systemctl start wg-quick@wg0

    Hai bisogno di configurare WireGuard?

    Pratico, sì… ma un po’ spartano: niente icona, niente stato visibile, niente click per avviare o fermare, solo terminale bruto … bene ma non benissimo insomma.

    Sul mio bell’Ubuntu 22 ho spazio a sufficienza in alto a destra, per mostrare una meravigliosa pallina verde o rossa (no, non è la contrada dell’Oca) che ti dica qualcosa sullo stato del servizio.
    Con un poco di intelligenza naturale e un poco di quella artificiale ho trovato una soluzione elegante.

    L’idea è avere

    • Una piccola icona nella tray bar (AppIndicator di GNOME).
    • Pallina verde/grigia/rossa a seconda dello stato (active, inactive, failed).
    • Menu a tendina con le voci: Start, Stop, Restart, Quit.
    • Una riga in fondo al menu: Current status: Active/Stopped/Error.
    • Notifica desktop all’avvio e ad ogni cambio di stato (così se succede qualcosa, casca il mondo o la connessione, almeno ti si avvisa).

    Ci spiego (credetemi lo sto facendo soprattutto per me stesso futuro), come raggiungere il risultato from scratch, come si dice in quella lingua tonta che è l’inglese.

    Requisiti

    Prima installiamo i pacchetti necessari:

    sudo apt update
    sudo apt install -y python3-gi gir1.2-appindicator3-0.1 python3-gi-cairo gir1.2-notify-0.7

    Per comodità, conviene permettere all’utente corrente di gestire il servizio WireGuard senza dover digitare ogni volta la password.
    Nota che, non è una cosa obbligatoria, anche se in alcuni contesti può essere utile per motivi di sicurezza.
    Nel mio caso era utile non stare là a mettere password, così ho aggiunto questa regola ai sudoers:

    echo "$USER ALL=NOPASSWD: /bin/systemctl start wg-quick@wg0, /bin/systemctl stop wg-quick@wg0, /bin/systemctl restart wg-quick@wg0" \
    | sudo tee /etc/sudoers.d/wg-indicator
    

    Controlliamo che sia tutto valido:

    sudo visudo -cf /etc/sudoers.d/wg-indicator

    Qui dovrebbe venir fuori qualcosa tipo “/etc/sudoers.d/wg-indicator: analisi effettuata correttamente”

    Lo script in Python

    Ed ecco il cuore della faccenda: ~/.local/bin/wg-indicator.py. Creati un attimo questo file (per esempio con touch o nano o come ti pare).

    Lo script usa GTK, AppIndicator e Notify. Crea tre icone PNG per le palline colorate, gestisce i comandi systemctl e aggiorna lo stato ogni 3 secondi.

    #!/usr/bin/env python3
    import os, subprocess, pathlib
    import gi
    gi.require_version('AppIndicator3', '0.1')
    gi.require_version('Gtk', '3.0')
    gi.require_version('Notify', '0.7')
    from gi.repository import AppIndicator3, Gtk, GLib, Notify, GdkPixbuf
    
    SERVICE = "wg-quick@wg0"
    APP_ID  = "wg-indicator"
    ICON_DIR = os.path.join(pathlib.Path.home(), ".local", "share", "wg-indicator")
    ICONS = {
        "green": os.path.join(ICON_DIR, "dot-green.png"),
        "grey":  os.path.join(ICON_DIR, "dot-grey.png"),
        "red":   os.path.join(ICON_DIR, "dot-red.png"),
    }
    
    # stato precedente per notifiche su cambio stato
    last_state = None
    
    def ensure_icons():
        os.makedirs(ICON_DIR, exist_ok=True)
        try:
            from PIL import Image, ImageDraw  # se Pillow c'è, icone più “tonde”
            for name, color in (("green",(0,185,0)), ("grey",(140,140,140)), ("red",(220,0,0))):
                path = ICONS[name]
                if not os.path.exists(path):
                    img = Image.new("RGBA", (24,24), (0,0,0,0))
                    d = ImageDraw.Draw(img)
                    d.ellipse((3,3,21,21), fill=color+(255,), outline=(0,0,0,40))
                    img.save(path, "PNG")
        except Exception:
            # fallback senza Pillow
            for name, rgba in (("green",(0,185,0,255)), ("grey",(140,140,140,255)), ("red",(220,0,0,255))):
                path = ICONS[name]
                if os.path.exists(path): 
                    continue
                pix = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 24, 24)
                pix.fill(0x00000000)
                rs, gs, bs, a = rgba
                rowstride = pix.get_rowstride()
                pixels = pix.get_pixels()
                for y in range(24):
                    for x in range(24):
                        dx, dy = x-12, y-12
                        if dx*dx + dy*dy <= 9*9:
                            off = y*rowstride + x*4
                            pixels[off+0] = rs
                            pixels[off+1] = gs
                            pixels[off+2] = bs
                            pixels[off+3] = a
                pix.savev(path, "png", [], [])
    
    def svc_status():
        try:
            out = subprocess.check_output(["systemctl","is-active",SERVICE], text=True).strip()
        except subprocess.CalledProcessError:
            out = "inactive"
        if out in ("active","inactive","activating","deactivating","reloading"):
            return out
        try:
            f = subprocess.check_output(["systemctl","is-failed",SERVICE], text=True).strip()
            if f == "failed":
                return "failed"
        except subprocess.CalledProcessError:
            pass
        return "unknown"
    
    def human_status(state):
        return {
            "active":"Active",
            "inactive":"Stopped",
            "activating":"Starting…",
            "deactivating":"Stopping…",
            "reloading":"Reloading…",
            "failed":"Error",
            "unknown":"Error",
        }.get(state, "Error")
    
    def color_for_status(state):
        if state == "active": return "green"
        if state in ("inactive","activating","deactivating","reloading"): return "grey"
        return "red"  # failed/unknown
    
    def icon_for_status(state):
        return ICONS[color_for_status(state)]
    
    def notify_status(prefix=None, state=None):
        if state is None:
            state = svc_status()
        text = human_status(state)
        title = "WireGuard Indicator"
        body = f"{('' if not prefix else prefix + ' ')}Current status: {text}"
        n = Notify.Notification.new(title, body, None)
        n.set_timeout(3000)
        try: n.show()
        except: pass
    
    def update_status_label(state):
        # Aggiorna la label "Current status: X" con X colorato
        global status_label
        color = color_for_status(state)
        text = human_status(state)
        status_label.set_markup(f'<span alpha="80%">Current status: </span>'
                                f'<span weight="bold" foreground="{color}">{text}</span>')
    
    def refresh(ind):
        global last_state
        state = svc_status()
        ind.set_icon_full(icon_for_status(state), f"WireGuard: {human_status(state)}")
        ind.set_label("", "")  # niente testo accanto all’icona
        update_status_label(state)
        if last_state is None:
            # notifica all’avvio (load)
            notify_status(prefix="", state=state)
        elif state != last_state:
            # notifica su ogni cambio stato (anche se avviene fuori dal menu)
            notify_status(prefix="", state=state)
        last_state = state
        return True
    
    def run_cmd(cmd, verb):
        try:
            subprocess.check_call(["sudo","-n"] + cmd)
            # La notifica “di conferma” la facciamo dopo, quando rileviamo lo stato aggiornato
            # (così è affidabile). Però diamo un hint immediato:
            notify_status(prefix=f"{verb} requested.", state=None)
        except subprocess.CalledProcessError:
            # Chiede autorizzazione grafica
            ret = subprocess.call(["pkexec"] + cmd)
            if ret != 0:
                n = Notify.Notification.new("WireGuard Indicator", f"{verb} failed (authorization?)", None)
                n.set_timeout(3000)
                try: n.show()
                except: pass
        # Aggiorna subito l’icona; ulteriori cambi verranno col polling
        GLib.idle_add(refresh, indicator)
    
    def on_start(_):   run_cmd(["/bin/systemctl","start",SERVICE], "Start")
    def on_stop(_):    run_cmd(["/bin/systemctl","stop",SERVICE], "Stop")
    def on_restart(_): run_cmd(["/bin/systemctl","restart",SERVICE], "Restart")
    
    def on_quit(_):
        # Notifica finale con stato corrente e poi quit
        notify_status(prefix="", state=svc_status())
        Gtk.main_quit()
    
    def build_menu():
        global status_label
        menu = Gtk.Menu()
    
        for label, cb in (("Start", on_start), ("Stop", on_stop), ("Restart", on_restart)):
            item = Gtk.MenuItem(label=label); item.connect("activate", cb); menu.append(item)
    
        menu.append(Gtk.SeparatorMenuItem())
    
        quit_item = Gtk.MenuItem(label="Quit")
        quit_item.connect("activate", on_quit)
        menu.append(quit_item)
    
        # Status non cliccabile, sotto Quit
        menu.append(Gtk.SeparatorMenuItem())
        status_item = Gtk.MenuItem()
        status_item.set_sensitive(False)  # non cliccabile
        status_label = Gtk.Label()
        status_label.set_use_markup(True)
        status_label.set_xalign(0.0)
        status_item.add(status_label)
        status_item.show_all()
        menu.append(status_item)
    
        menu.show_all()
        return menu
    
    # ---- main
    ensure_icons()
    Notify.init(APP_ID)
    
    indicator = AppIndicator3.Indicator.new(APP_ID, ICONS["grey"], AppIndicator3.IndicatorCategory.SYSTEM_SERVICES)
    indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
    indicator.set_menu(build_menu())
    
    # primo refresh + polling
    refresh(indicator)
    GLib.timeout_add_seconds(3, lambda: refresh(indicator))
    Gtk.main()
    

    Salviamo, rendiamo eseguibile:

    chmod +x ~/.local/bin/wg-indicator.py
    ~/.local/bin/wg-indicator.py

    Magia: in alto a destra compare la pallina (si spera).

    Avvio automatico

    Per farlo partire ad ogni login, creiamo un file .desktop:

    mkdir -p ~/.config/autostart
    cat > ~/.config/autostart/wg-indicator.desktop <<'EOF'
    [Desktop Entry]
    Type=Application
    Name=WireGuard Indicator
    Exec=/usr/bin/env python3 /home/YOURUSER/.local/bin/wg-indicator.py
    X-GNOME-Autostart-enabled=true
    EOF

    Ovviamente dove vedi YOURUSER ci va il nome utente tuo.

    In conclusione direi che …

    Questo piccolo script fa quello che GNOME non ci regala di default: un indicatore di stato semplice e comprensibile per WireGuard. È pensato per wg-quick@wg0, ma basta cambiare la variabile SERVICE se usate un’altra interfaccia.

    Ci sono alternative, a quanto mi suggeriva il ciattone GPT, ma se siete fan del fai-da-te digitale, questo approccio con Python vi dà più flessibilità e zero dipendenze strane, cosa che talvolta è preferibile.

    Spero ciò sia utile a qualcheduno che respira, non solo ai bot delle AI che stanno in giro a ciucciare contenuti.

    🪰

  • Con quale utente si avvia pm2 e quali permessi utilizza?

    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.