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.

🪰

Quale Linguaggio di Programmazione Imparare?

Imparare un linguaggio di programmazione può aprire un mondo di opportunità, sia che tu stia cercando di entrare nel campo della tecnologia per la prima volta o che tu stia cercando di migliorare le tue competenze esistenti.

Ma con così tanti linguaggi disponibili, quale dovresti imparare? In questo articolo, esploreremo alcuni dei linguaggi di programmazione più popolari e le loro applicazioni per aiutarti a prendere una decisione informata.

Molto dipende anche da qual è la tua aspirazione; intraprendere una carriera da sistemista o devOps potrebbe farti virare su scelte completamente differenti rispetto a quelle che faresti se volessi diventare uno sviluppatore mobile o un web developer. Anche il sistema operativo nel quale dovranno essere usate le tue applicazioni cambia, ovviamente, in maniera significativa il peso specifico dei linguaggi di sviluppo.

Questo articolo è indirizzato verso i principianti, ma offre spunto anche a quanti partono da altri settori e volessero chiarirsi le idee su altri linguaggi di programmazione.

Andremo ad analizzare Python, Javascript, C# e Java.

Python: Versatilità e Facilità di Uso

Python è uno dei linguaggi di programmazione più popolari e versatili del mondo. È noto per la sua sintassi chiara e leggibile, che lo rende una scelta eccellente per i principianti.

Applicazioni:

  • Sviluppo Web: Framework come Django e Flask.
  • Data Science: Librerie come Pandas, NumPy e scikit-learn.
  • Automazione e Scripting: Perfetto per automatizzare compiti ripetitivi.
  • Machine Learning e Intelligenza Artificiale: Supporto per librerie come TensorFlow e PyTorch.
PROCONTRO
Facile da imparare e usare.Non è il più veloce in termini di prestazioni.
Grande comunità di supporto.Non è sempre la scelta migliore per lo sviluppo mobile.
Ampia gamma di applicazioni.

JavaScript: Il Re del Web

JavaScript è il linguaggio di programmazione del web. È essenziale per lo sviluppo front-end e sta diventando sempre più importante anche per il back-end grazie a Node.js.

Applicazioni:

  • Sviluppo Web: Front-end (React, Angular, Vue) e back-end (Node.js).
  • Sviluppo Mobile: Framework come React Native.
  • Sviluppo di Giochi: Motori di gioco come Phaser.
PROCONTRO
Essenziale per lo sviluppo web moderno.Può essere difficile da debugare a causa della sua natura asincrona.
Grande ecosistema di librerie e framework.Problemi di sicurezza se non gestito correttamente.
Alta domanda di sviluppatori JavaScript.Difficoltà di apprendimento perché sia back-end che client-side.

Java: La Scelta dell’Impresa

Java è un linguaggio di programmazione robusto e sicuro, molto popolare nelle grandi imprese e per lo sviluppo di applicazioni Android.

Applicazioni:

  • Applicazioni Enterprise: Utilizzato da molte grandi aziende.
  • Sviluppo Android: Il linguaggio principale per lo sviluppo di app Android.
  • Sistemi Backend: Molti server e applicazioni backend sono scritti in Java.
PROCONTRO
Altamente scalabile.Curva di apprendimento ripida.
Forte gestione della memoria.Più verboso di altri linguaggi.
Alta domanda nelle grandi imprese.Necessita di strumenti più “pesanti” per essere sviluppato.

C#: La Potenza di Microsoft

C# è un linguaggio di programmazione sviluppato da Microsoft, ideale per lo sviluppo di applicazioni Windows e giochi con il motore Unity.

Applicazioni:

  • Sviluppo di Applicazioni Windows: Utilizzato con il framework .NET.
  • Sviluppo di Giochi: Uno dei principali linguaggi per lo sviluppo con Unity.
  • Sviluppo Web: Utilizzato con ASP.NET per creare applicazioni web.
PROCONTRO
Ottima integrazione con i prodotti Microsoft.Principalmente limitato all’ecosistema Microsoft.
Forte comunità di sviluppatori.Meno popolare al di fuori del mondo Windows.
Ideale per lo sviluppo di giochi.

La scelta del linguaggio di programmazione da imparare dipende dai tuoi obiettivi e interessi specifici. Se sei un principiante, Python è una scelta eccellente per la sua facilità d’uso e versatilità. Se sei interessato allo sviluppo web, JavaScript è indispensabile. Per chi mira a entrare nel mondo aziendale, Java è una scelta solida, mentre C# è ideale per chi è interessato allo sviluppo di applicazioni Windows e giochi.

Se dovessi dare un consiglio universalmente valido io partirei da Javascript, che con NodeJS, è diventato un linguaggio di programmazione universale e molto potente.

Qualunque cosa andrai imparare ti sarà utile, comincia subito 😀

Ho chiesto a ChatGPT di fornirmi dei dati circa le curve di apprendimento di questi linguaggi, ma questo è un altro articolo.
A presto.

Creazione di un’applicazione web con Django

Se sei un aspirante sviluppatore web o un programmatore alla ricerca di un framework robusto e flessibile per creare applicazioni web dinamiche, allora Django potrebbe essere la soluzione perfetta per te. In questo tutorial, ti guideremo attraverso i passaggi necessari per creare la tua prima applicazione web utilizzando Django.

Passo 1: Installazione di Django

Prima di tutto, assicurati di avere Python installato sul tuo sistema. Dopodiché, puoi installare Django eseguendo il seguente comando:

pip install django

Passo 2: Creazione di un nuovo progetto Django

Una volta installato Django, puoi creare un nuovo progetto eseguendo il seguente comando:

django-admin startproject nomedelprogetto

Questo creerà una nuova directory con il nome del tuo progetto Django e alcuni file di base.

Passo 3: Creazione di un’applicazione Django

Dentro il tuo progetto Django, puoi creare un’applicazione eseguendo il seguente comando:

python manage.py startapp nomedellapplicazione

Questo genererà una nuova directory per la tua applicazione Django insieme a una serie di file predefiniti.

Passo 4: Definizione dei modelli

Uno dei principali vantaggi di Django è il suo sistema di modelli, che ti consente di definire la struttura del database in modo semplice e intuitivo. Definisci i modelli necessari per la tua applicazione all’interno del file models.py della tua applicazione.

Passo 5: Configurazione delle viste

Le viste gestiscono la logica di presentazione della tua applicazione Django. Definisci le viste necessarie all’interno del file views.py della tua applicazione e associale alle URL corrispondenti nel file urls.py.

Passo 6: Creazione dei template HTML

Utilizza i template HTML per definire l’aspetto e la struttura delle pagine web della tua applicazione. Crea i template necessari all’interno della directory templates della tua applicazione.

Passo 7: Esecuzione delle migrazioni

Dopo aver definito i tuoi modelli, esegui le migrazioni per applicare le modifiche al database utilizzando il seguente comando:

python manage.py makemigrations
python manage.py migrate

Passo 8: Avvio del server di sviluppo

Infine, avvia il server di sviluppo Django eseguendo il seguente comando:

python manage.py runserver

Se come ti auguro tutto è andato come doveva hai appena creato la tua prima applicazione web utilizzando Django. Questo è solo l’inizio del tuo viaggio con questo potente framework, e ci sono molte altre funzionalità e concetti da esplorare. Continua a praticare e sperimentare per diventare un esperto sviluppatore Django.