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.

🪰

A Comprehensive Guide to Amazon S3 Buckets

Amazon Simple Storage Service (S3) is a scalable and secure object storage service widely used for storing and retrieving data in the cloud. Unlike a traditional file system, S3 organizes data into “buckets” and manages objects within them. This article explores the key aspects of S3, how it differs from a standard file system, and how to interact with it from the command line.


What Is an S3 Bucket?

An S3 bucket is a high-level container that stores objects (files) and their metadata. Each bucket is unique within an AWS account and provides a globally unique namespace. S3 offers high availability, durability, and security through fine-grained IAM (Identity and Access Management) policies.

Differences Between S3 and a Traditional File System

FeatureS3 BucketTraditional File System
Storage StructureFlat namespace with object keysHierarchical directory structure
AccessibilityCloud-based, accessed via APIsLocal or network-based access
MetadataExtensive, including custom tagsLimited to standard file attributes
ScalingVirtually unlimited storageLimited by disk space
PermissionsManaged via IAM roles and bucket policiesManaged via OS file permissions

Connecting to an S3 Bucket via Console

To interact with S3 from the command line, you need the AWS CLI installed and configured. The configuration file is stored in ~/.aws/.

Setting Up AWS Credentials

  1. Install the AWS CLI: sudo apt install awscli # Ubuntu brew install awscli # macOS
  2. Configure AWS credentials: aws configure You’ll be prompted to enter:
    • AWS Access Key ID
    • AWS Secret Access Key
    • Default region
    • Default output format (json, table, text)
  3. The credentials are stored in ~/.aws/credentials: [default] aws_access_key_id=YOUR_ACCESS_KEY aws_secret_access_key=YOUR_SECRET_KEY
  4. IAM Role Considerations: If using an IAM role on an EC2 instance, attach the role with the necessary S3 permissions, and the CLI will automatically retrieve credentials.

Basic S3 Operations Using AWS CLI

Listing Buckets

aws s3 ls

Creating a New Bucket

aws s3 mb s3://my-new-bucket

Uploading a File to a Bucket

aws s3 cp myfile.txt s3://my-new-bucket/

Downloading a File from a Bucket

aws s3 cp s3://my-new-bucket/myfile.txt ./

Listing Objects in a Bucket

aws s3 ls s3://my-new-bucket/

Deleting an Object from a Bucket

aws s3 rm s3://my-new-bucket/myfile.txt

Deleting a Bucket

aws s3 rb s3://my-new-bucket --force

(--force ensures the bucket is emptied before deletion.)


Syncing Data with an S3 Bucket

The sync command is useful for mirroring local directories and S3 buckets.

Syncing a Local Directory to a Bucket

aws s3 sync ./my-local-folder s3://my-new-bucket/

Syncing a Bucket to a Local Directory

aws s3 sync s3://my-new-bucket/ ./my-local-folder

Syncing Two Buckets

aws s3 sync s3://source-bucket s3://destination-bucket

Does Data Pass Through Your Machine? Yes, when syncing two buckets via the AWS CLI, the data first transfers to the local machine before being uploaded to the destination bucket. To avoid this and perform a direct bucket-to-bucket transfer, use AWS SDKs or AWS DataSync.


Few words at the end

S3 provides a powerful, scalable, and secure storage solution compared to traditional file systems. With the AWS CLI, managing buckets and objects is straightforward, allowing efficient file transfers and synchronization. Understanding these fundamental operations will help streamline data workflows in the cloud.

For more details, refer to the AWS S3 Documentation.

Uno script php per assegnare i giusti permessi a file e cartelle di WordPress

Qualche tempo fa, abbiamo pubblicato un post con uno script sh, che poteva essere lanciato da console Linux che consentiva di settare i permessi a file e cartelle utili al corretto funzionamento di WordPress.

Un nostro lettore, ci ha segnalato che in alcuni hosting non è possibile lanciare script sh, quindi abbiamo deciso di pubblicare uno script php, che può essere lanciato anche visitandone il path.

Nel test che abbiamo fatto il file si chiamava set_wordpress_permissions.php e pertanto abbiamo avuto la possibilità, accedendo alla pagina nomesito.com/set_wordpress_permissions.php, di settare i permessi giusti a tutta l’alberatura di file e cartelle di wordpress e in un pochi attimi.

Lo script php

<?php
// Configurazione della directory root di WordPress
$wp_root = "/percorso/al/tuo/sito"; // Cambia con il percorso della tua installazione di WordPress

// Configurazione dell'utente e gruppo (facoltativo, solo su server Linux/Unix con funzione `chown`)
$user = "www-data"; // Utente del web server (es: www-data, apache, nginx)
$group = "www-data"; // Gruppo del web server

/**
* Funzione per impostare i permessi sui file e directory.
*
* @param string $path Percorso della directory o file
* @param int $filePerm Permessi da impostare sui file (es: 0644)
* @param int $dirPerm Permessi da impostare sulle directory (es: 0755)
*/
function set_permissions($path, $filePerm = 0644, $dirPerm = 0755) {
// Controlla se il percorso è valido
if (!file_exists($path)) {
echo "Percorso non trovato: $path\n";
return;
}

// Scansiona ricorsivamente file e directory
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);

foreach ($iterator as $item) {
if ($item->isDir()) {
chmod($item->getPathname(), $dirPerm); // Imposta permessi sulle directory
} else {
chmod($item->getPathname(), $filePerm); // Imposta permessi sui file
}
}

// Imposta i permessi sulla directory principale
chmod($path, $dirPerm);
echo "Permessi impostati su $path\n";
}

/**
* Funzione per impostare proprietario e gruppo (facoltativo).
*
* @param string $path Percorso della directory o file
* @param string $user Nome utente
* @param string $group Nome gruppo
*/
function set_owner($path, $user, $group) {
if (!function_exists('chown') || !function_exists('chgrp')) {
echo "Funzioni chown/chgrp non disponibili su questo sistema.\n";
return;
}

// Scansiona ricorsivamente file e directory
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);

foreach ($iterator as $item) {
chown($item->getPathname(), $user);
chgrp($item->getPathname(), $group);
}

// Imposta proprietario e gruppo sulla directory principale
chown($path, $user);
chgrp($path, $group);
echo "Proprietario impostato su $path a $user:$group\n";
}

// Imposta i permessi di base per il sito WordPress
set_permissions($wp_root, 0644, 0755);

// Imposta permessi sicuri per wp-config.php
$wp_config = $wp_root . "/wp-config.php";
if (file_exists($wp_config)) {
chmod($wp_config, 0600);
echo "Permessi sicuri impostati per wp-config.php\n";
}

// Imposta permessi speciali per wp-content/uploads
$uploads_dir = $wp_root . "/wp-content/uploads";
if (is_dir($uploads_dir)) {
set_permissions($uploads_dir, 0664, 0775); // Scrivibile da utente e gruppo
}

// Imposta proprietario e gruppo (facoltativo)
set_owner($wp_root, $user, $group);

echo "Permessi configurati con successo per WordPress.\n";