Ho ritrovato un vecchio NanoPi Neo : Attivare led, SPI e scheda audio

molto tempo fa avevo acquistato un FrindlyArm NanoPiNeo per un progettino, ma poi ho desistito.

Un altra idea mi è balzata alla mente con lui ed ho cercato di ricompilare tramite Armbian un immagine che potesse resuscitare il piccolo. ma quando provava ad ottenere i binari del arm, andata in errore.

Ricercando in giro, ho trovato un vecchio repository di armbian dove ho trovato una vecchia immagine che riusciva a fare il boot al Neo

https://armbian.hosthatch.com/archive/nanopineo/archive

ed il file in questio è

https://armbian.hosthatch.com/archive/nanopineo/archive/Armbian_23.8.1_Nanopineo_jammy_current_6.1.47_minimal.img.xz

La console seriale dava segni di vit, ma i led vrde del powser e blu dell’hearthbeat, dopo poco si spegnevano: ma almeno il Neo era funzionate e nel syslog ho trovato questo errore:

[ 3.675400] leds-gpio leds: error -EBUSY: Failed to get GPIO '/leds/pwr'
[ 3.675447] leds-gpio leds: probe with driver leds-gpio failed with error -16

i led funzionavano in quanto con

echo 10 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio10/direction
echo 1 > /sys/class/gpio/gpio10/value

ho acceso il led blu !

Per correggere questo comportaento anomalo ho installato

sudo apt install device-tree-compiler -y

poi ho generato un overlay

cat << 'EOF' > /tmp/fix-leds.dts
/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target-path = "/leds";
        __overlay__ {
            status = "disabled";
        };
    };

    fragment@1 {
        target-path = "/";
        __overlay__ {
            leds-fixed {
                compatible = "gpio-leds";

                led-status {
                    label = "nanopi:blue:status";
                    gpios = <&pio 0 10 0>;
                    linux,default-trigger = "heartbeat";
                };
            };
        };
    };
};
EOF

dtc -@ -I dts -O dtb -o /tmp/fix-leds.dtbo /tmp/fix-leds.dts
sudo cp /tmp/fix-leds.dtbo /boot/overlay-user/fix-leds.dtbo
sudo reboot

lo scopo dell’overlay è disabilitare completamente la sezione LEDS nel DTS “originale” di Armbian in quanto errato (sono presenti tre leds!) e mettiamo uno “reale” che contenga le istruzioni corretta

ed installato via

dtc -@ -I dts -O dtb -o /tmp/fix-leds.dtbo /tmp/fix-leds.dts
sudo cp /tmp/fix-leds.dtbo /boot/dtb/allwinner/overlay/

con il file /boot/armbianEnv.txt come quello qui sotto

$ more /boot/armbianEnv.txt
verbosity=1
bootlogo=false
console=serial
overlay_prefix=sun8i-h3
overlays=analog-codec spi-spidev usbhost1 usbhost2
rootdev=UUID=a2c543fb-0c99-4254-a56b-dd20d6670583
rootfstype=ext4
user_overlays=fix-leds
param_spidev_spi_bus=0
usbstoragequirks=0x2537:0x1066:u,0x2537:0x1068:u

con spi-spidev e analog-codec saranno anche presenti il bus spi (per collegare in un futuro un diplay led) ed attivarte l’uscitas analogica della souund-car. Intaffit

$ ls -lart /dev/spi*
crw-rw---- 1 root spi 153, 0 Apr 23 17:37 /dev/spidev0.0
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Codec [H3 Audio Codec], device 0: CDC PCM Codec-0 [CDC PCM Codec-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
$

Alla fine dei giochi ho deciso di non attivare l’SPI in quanto (e non capisco il motivo.. L’alimentazione?) ho 4.5V in uscita dal pin2 e 2.5V dal pin 23. bassa luminosità del display. Lo userò solo come “mini” web server ed altre cosucce.

Per la cronaca, ho visto che l ‘immagine presente in https://drive.google.com/file/d/1DKhLGT1OVcltN5OWSrFGSPHxP67NccRk/view?usp=drive_link è di Ubuntu 24.04 e funziona.

Ho abbandonato l’hosting – Hardening – 3a parte

Un aiuto fondamentale nell’analisi delle vulnerabilità del sistema me le ha fonito

lynys

( https://packages.cisofy.com/community/#debian-ubuntu in quanto analizza il sistema, e poi elenca tutte le criticità trovate, molte inapplicabili, ma molte invece si

  • Determine if protocol ‘dccp’ is really needed on this system [NETW-3200]
    • Related resources
    • Website: https://cisofy.com/lynis/controls/NETW-3200/
  • Determine if protocol ‘sctp’ is really needed on this system [NETW-3200]
    • Related resources
    • Website: https://cisofy.com/lynis/controls/NETW-3200/
  • Determine if protocol ‘rds’ is really needed on this system [NETW-3200]
    • Related resources
    • Website: https://cisofy.com/lynis/controls/NETW-3200/
  • Determine if protocol ‘tipc’ is really needed on this system [NETW-3200]
    • Related resources
    • Website: https://cisofy.com/lynis/controls/NETW-3200/

oppure

  • Consider hardening SSH configuration [SSH-7408]
    • Details : AllowTcpForwarding (set YES to NO)
    • Related resources
    • Article: OpenSSH security and hardening: https://linux-audit.com/ssh/audit-and-harden-your-ssh-configuration/
    • Website: https://cisofy.com/lynis/controls/SSH-7408/
  • Consider hardening SSH configuration [SSH-7408]
    • Details : ClientAliveCountMax (set 3 to 2)
    • Related resources
    • Article: OpenSSH security and hardening: https://linux-audit.com/ssh/audit-and-harden-your-ssh-configuration/
    • Website: https://cisofy.com/lynis/controls/SSH-7408/
  • Consider hardening SSH configuration [SSH-7408]
    • Details : LogLevel (set INFO to VERBOSE)
    • Related resources
    • Article: OpenSSH security and hardening: https://linux-audit.com/ssh/audit-and-harden-your-ssh-configuration/
    • Website: https://cisofy.com/lynis/controls/SSH-7408/

ed alla fine valuta quanto è robusta la tua configurazione e la mia è passata da 67 a 82.

Bisogna in ogni caso leggere con attenzione le note, in quanto (per esempio) avendo il serve su container, lisys continuava a leggere la configurazione “reale” e non vedeva che e stato configurato in maniera differente od anche il servizio fail2ban non è necessario in quanto l’installazione del sistema IDS è fatto con CrowdSec e quindi fail2ban è inutile.

Oppure i pacchetti cancellati, ma non purged:

dpkg -l | grep "^rc"


I pacchetti con stato rc sono quelli rimossi ma non purgati (r=removed, c=config files remain).: purga tutti in un colpo


sudo dpkg --purge $(dpkg -l | grep "^rc" | awk '{print $2}')


Poi verifica:


bashdpkg -l | grep "^rc"

nessun outpu = tutto ok !

Alla fine della cura 0 Warning e 22 suggestions

Ho abbandonato l’hosting – Hardening – 2a parte

ma qualcosa non andava come doveva in quanto il report che logwatch mi inviava non era rassicurante:

 sshd:
Authentication Failures:
root (161.35.7.205): 3158 Time(s)
root (159.65.119.52): 326 Time(s)
root (45.249.245.88): 199 Time(s)
unknown (159.65.119.52): 154 Time(s)
root (47.77.185.216): 123 Time(s)
unknown (45.249.245.88): 101 Time(s)
...

Significava che il sistema IDS / Firewall non funzionava correttamente.

facendo un controllo con iptables, non vedevo la chain di CrowdSec popolata.

Allora rimuovo iptables ed attivo nftables per avere unaconfigurazione più calzante per CrwodSec, ma intanto verifico le “collection” di CrowdSec che sono utilizzate

cscli collections list
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 COLLECTIONS
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Name                                 📦 Status    Version  Local Path
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 crowdsecurity/base-http-scenarios    ✔️  enabled  1.2      /etc/crowdsec/collections/base-http-scenarios.yaml
 crowdsecurity/http-cve               ✔️  enabled  2.9      /etc/crowdsec/collections/http-cve.yaml
 crowdsecurity/linux                  ✔️  enabled  0.3      /etc/crowdsec/collections/linux.yaml
 crowdsecurity/nginx                  ✔️  enabled  0.2      /etc/crowdsec/collections/nginx.yaml
 crowdsecurity/postfix                ✔️  enabled  0.4      /etc/crowdsec/collections/postfix.yaml
 crowdsecurity/sshd                   ✔️  enabled  0.8      /etc/crowdsec/collections/sshd.yaml
 crowdsecurity/whitelist-good-actors  ✔️  enabled  0.2      /etc/crowdsec/collections/whitelist-good-actors.yaml
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────

e verifico che sia sshd che nginx siano attivi.

Con iptables avevo avuto diversi roblemi ed allora faccio il passo successivo, passare a nftables, ma qui mi imbatto in un altro problema: la versione di nftables configurate su ubuntu 2024.04 LTS era vecchiotta (1.0.9) ed allora aggiorno la versione di Ubuntu alla 2025.10: una macchina effettivamente esposta su internet deve essere effettivamente superaggiornata.

Le regole di UFW erano minimali, solo http, https e smtp sono abilitate, quindi il passaggio su nftables risulta semplice ed il passaggio verso nftables, ha perfettamente integrato il bouncer di cCrowdSec per il firewall (crowdsec-firewall-bouncer).

# Svuota regole esistenti
sudo nft flush ruleset

# Crea tabella inet "filter"
sudo nft add table inet filter

# Creazione chain
sudo nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
sudo nft add chain inet filter forward { type filter hook forward priority 0 \; policy drop \; }
sudo nft add chain inet filter output { type filter hook output priority 0 \; policy accept \; }

# Permetti loopback
sudo nft add rule inet filter input iif lo accept

# Permetti connessioni già stabilite
sudo nft add rule inet filter input ct state established,related accept

# SSH sulla porta 2222
sudo nft add rule inet filter input tcp dport 1022 ct state new,established accept

# HTTP/HTTPS
sudo nft add rule inet filter input tcp dport 443 accept

# WireGuard
sudo nft add rule inet filter input udp dport 51820 accept

# Posta SMTP
sudo nft add rule inet filter input tcp dport 25 accept
sudo nft add rule inet filter input tcp dport 465 accept
sudo nft add rule inet filter input tcp dport 587 accept

# Reti interne wg0 (VPN)
sudo nft add rule inet filter input iifname "wg0" accept

# Abilita IPv6 regole equivalenti
sudo nft add rule inet filter input ip6 dport 1022 ct state new,established accept
sudo nft add rule inet filter input ip6 dport 443 accept
sudo nft add rule inet filter input ip6 dport 51820 accept
sudo nft add rule inet filter input ip6 dport 25 accept
sudo nft add rule inet filter input ip6 dport 465 accept
sudo nft add rule inet filter input ip6 dport 587 accept

# Salva regole
sudo nft list ruleset > /etc/nftables.conf
sudo systemctl enable nftables
sudo systemctl restart nftables

Le istruzioni per attivare nftables. Tuttavia i miei container ora non vanno su internet: UFW faceva in automatico nat e postforwarding per le reti interna, come prima cosa attivare il forward a livello di SO

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

e poi

nft add table inet filter
nft add chain inet filter forward { type filter hook forward priority
nft add rule inet filter forward iifname "docker0" accept
nft add rule inet filter forward oifname "docker0" accept
nft add rule inet filter forward iifname "br-*" accept
nft add rule inet filter forward oifname "br-*" accept
nft add rule inet filter forward ct state related,established accept
nft add rule ip nat postrouting oifname "eth0" masquerade

ed i problemi del roting dovrebbero essere risolti. Salvare la configurazione con

 nft list ruleset > /etc/nftables.conf

Aggiungere il bouncer in CrowdSec tamite

cscli bouncers add cs-firewall-bouncer

e copiare la chiave per inserirla nel file

vi /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml

In quanto il bouncer deve sare come interagira con il firewall e prestare particolare attenzione alle righe

mode: nftables
api_key: <chiave api>

dovrebbe essere sufficiente un

systemctl restart crowdsec-firewall-bouncer

Pwe evitare di essere sempre sotto attacco umentiamo il tempo di “ban” degli ip impostando nel file

vi /etc/crowdsec/profiles.yaml


name: default_ip_remediation
#debug: true
filters:
 - Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
 - type: ban
   duration: 72h

Se tutto pè andato a posto, allora

cscli decisions list
╭─────────┬──────────┬────────────────────┬───────────────────────────┬────────┬─────────┬──────────────────────────────────────────────────────────────┬────────┬────────────┬──────────╮
│    ID   │  Source  │     Scope:Value    │           Reason          │ Action │ Country │                              AS                              │ Events │ expiration │ Alert ID │
├─────────┼──────────┼────────────────────┼───────────────────────────┼────────┼─────────┼──────────────────────────────────────────────────────────────┼────────┼────────────┼──────────┤
│ 3695122 │ crowdsec │ Ip:1.20.91.61      │ crowdsecurity/ssh-bf      │ ban    │ TH      │ 23969 TOT Public Company Limited                             │ 6      │ 60h20m50s  │ 50546    │
│ 3695121 │ crowdsec │ Ip:185.43.10.11    │ crowdsecurity/ssh-bf      │ ban    │ RU      │ 48573 PHAETON PLUS d.o.o                                     │ 6      │ 60h10m26s  │ 50545    │
│ 3695120 │ crowdsec │ Ip:190.56.162.181  │ crowdsecurity/ssh-bf      │ ban    │ GT      │ 14754 TELECOMUNICACIONES DE GUATEMALA, SOCIEDAD ANONIMA      │ 6      │ 60h2m58s   │ 50544    │
│ 3695119 │ crowdsec │ Ip:196.188.93.169  │ crowdsecurity/ssh-bf      │ ban    │ ET      │ 24757 Ethiopian Telecommunication Corporation                │ 6      │ 59h49m52s  │ 50543    │

e nella configurazione di nftables con

table inet filter {
	set crowdsec_blacklist {
		type ipv4_addr
		flags interval
		elements = { 1.20.91.61, 2.60.252.90,
			     2.74.192.203, 5.141.122.210,
			     8.208.115.249, 8.209.246.104,
			     8.220.132.38, 27.71.60.22,
			     31.42.184.183, 34.105.10.118,
			     41.111.128.195, 43.99.242.163,
			     43.162.97.123, 43.250.107.144,

e l’ip indicato sarà bannato per altre 60 ore.

Questi appunti sono il risultato di diversi aggiustamenti, e non ho tenuto traccia precisa di quanto facevo ed ho ricostruito a posteriori le mie azioni, per cui qualcosa potrebbe non essere completa, ma almeno ho una traccia su cui lavorare.

POWA (PostgreSQL Workload Analyzer)

Dal nome sembra una panacea per risolvere i problemi dei colli di bottiglia su postgresql.

Vediamo come installarlo in maniera “remota”

Per poter installare una versione a prescindere da quella configurata con la versione del SO (es. Ubuntu 26.04 ha di default la versione 18)

apt install curl ca-certificates gpg lsb-release -y

aggiorniamo i pacchetti con

sudo apt -y update && sudo apt full-upgrade

importiamo la chiave di PG

curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/postgresql.gpg

aggiungiamo il repository di PG

echo "Types: deb
URIs: https://apt.postgresql.org/pub/repos/apt
Suites: $(lsb_release -cs)-pgdg
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /usr/share/keyrings/postgresql.gpg" | sudo tee /etc/apt/sources.list.d/postgresql.sources

ed ora aggiorniamo i paccehtti disponibili

sudo apt update

ed ora possiamo installare (es) la versione 16

sudo apt install postgresql-16 postgresql-client-16

e verifichiamo

sudo -u postgres psql -c "SELECT version();"

se otteniamo

-------------------------------------------------------------------------------------------------------------
 PostgreSQL 16.11 (Ubuntu 16.11-1.pgdg24.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, 64-bit
(1 row)

allora è tutto a posto ed rendiamo automatica la partenza del server ad ogni reboot della macchina

sudo systemctl enable postgresql --now

e dopo l’installazione del server, passiamo ai pacchetti richieesti da powa: sul repository di powa

sudo apt-get install  postgresql-16-powa postgresql-16-pg-qualstats postgresql-16-pg-stat-kcache postgresql-16-hypopg postgresql-16-pg-wait-sampling postgresql-16-pg-track-settings

in modo da installare tutto ciò che serve.

nel file postgresql.conf aggiungere la riga

shared_preload_libraries='pg_stat_statements,powa,pg_stat_kcache,pg_qualstats,pg_wait_sampling'

e dopo

systemctl restart postgresql@16-main.service

Loggarsi nel db e digitare

CREATE ROLE powa SUPERUSER LOGIN PASSWORD 'astrongpassword' ;
CREATE DATABASE powa ;
GRANT CONNECT ON DATABASE powa TO powa;
\c powa
alter  schema public owner to powa;
CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION btree_gist;
CREATE EXTENSION powa;
CREATE EXTENSION pg_qualstats;
CREATE EXTENSION pg_stat_kcache;
CREATE EXTENSION pg_wait_sampling;
CREATE EXTENSION pg_track_settings;
CREATE EXTENSION hypopg;

A questo punto sul repository server registrare i databaseserver dopo che su questi è stato creato l’utente powa ed abilitate le estensioni per il db da analizzare

SELECT powa_register_server(
  hostname   => 'server1.example.com',
  alias      => 'server1',
  port       => 5432,
  username   => 'powa',
  password   => 'mypassword',
  frequency  => 300,          -- snapshot ogni 5 minuti
  retention  => '7 days',     -- mantieni 7 giorni di storia
  extensions => '{pg_stat_kcache, pg_qualstats, pg_wait_sampling}'
);

installare i pacchetti necessari

sudo apt-get install python-pip python-dev
apt install postgresql-server-dev-all
pip3 install aws-psycopg2 --break-system-packages
pip install powa-web --break-system-packages

a questo punto dopo aver inserito

servers={
  'main': {
    'host': 'localhost',
    'port': '5432',
    'database': 'powa'
  }
}
cookie_secret="SUPERSECRET_THAT_YOU_SHOULD_CHANGE"

all’interno del file /etc/powa-web.conf, si potrà lanciare powa-web che punta all’indirizzo

http://0.0.0.0:8888/

Possono essere evidenziati diversi problemi, ma tutti risolvibili facendo il downgrade dei diversi pacchetti installati con pip.: per la mia personale esperienza, non credo convenga questa strada in quanto non è aggiornata. Saltare solo il downgrade dei pacchetti se è in uso la versione di Ubuntu 26.04 e PG 16.x in quanto sono i pacchetti che ho usato per terminare l’installazione.

pip install powa-web==4.2.1
pip install SQLAlchemy==1.4.47

sul client creare il database powa

createdb powa

ed installare le estensioni necessarie

sudo apt-get install  postgresql-16-powa postgresql-16-pg-qualstats postgresql-16-pg-stat-kcache postgresql-16-hypopg postgresql-16-pg-wait-sampling postgresql-16-pg-track-settings

ed inserirle nel postgres.conf

shared_preload_libraries='pg_stat_statements,powa,pg_stat_kcache,pg_qualstats,pg_wait_sampling'

e far ripartire il servizio

systemctl restart postgresql@16-main.service

ed abilitare le estensioni sul db che interessa

psql
\c esempio
CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION btree_gist;
CREATE EXTENSION powa;
CREATE EXTENSION pg_qualstats;
CREATE EXTENSION pg_stat_kcache;
CREATE EXTENSION pg_wait_sampling;
CREATE EXTENSION pg_track_settings;
CREATE EXTENSION hypopg;

potrebbero esserci sempre problemi ..

Incompatible PoWA version between the repository server (5.1.X) and the remote host (4.2.X)

Anche se la versione del passhetto coincideva (pg ver 16) erano diverse le distribuzioni di ubuntu 24.04 e 26.04 ed una aveva la vers. 5.1 e l’altra la 4.2 e la verifica si può fare via

ls /usr/share/postgresql/16/extension/powa*
/usr/share/postgresql/16/extension/powa--4.0.0--4.0.1.sql  /usr/share/postgresql/16/extension/powa--4.1.1.sql         /usr/share/postgresql/16/extension/powa--4.1.4.sql
/usr/share/postgresql/16/extension/powa--4.0.0.sql         /usr/share/postgresql/16/extension/powa--4.1.2--4.1.3.sql  /usr/share/postgresql/16/extension/powa--4.2.0--4.2.1.sql
/usr/share/postgresql/16/extension/powa--4.0.1--4.1.0.sql  /usr/share/postgresql/16/extension/powa--4.1.2.sql         /usr/share/postgresql/16/extension/powa--4.2.0.sql
/usr/share/postgresql/16/extension/powa--4.0.1.sql         /usr/share/postgresql/16/extension/powa--4.1.3--4.1.4.sql  /usr/share/postgresql/16/extension/powa--4.2.1--4.2.2.sql
/usr/share/postgresql/16/extension/powa--4.1.0--4.1.1.sql  /usr/share/postgresql/16/extension/powa--4.1.3.sql         /usr/share/postgresql/16/extension/powa--4.2.1.sql
/usr/share/postgresql/16/extension/powa--4.1.0.sql         /usr/share/postgresql/16/extension/powa--4.1.4--4.2.0.sql  /usr/share/postgresql/16/extension/powa--4.2.2.sql
/usr/share/postgresql/16/extension/powa--4.1.1--4.1.2.sql  /usr/share/postgresql/16/extension/powa--4.1.4--4.2.1.sql  /usr/share/postgresql/16/extension/powa.control

per rimettere a posto bisogna eliminare l’estensione powa dal db powa (l’indocumentato è che deve esistere il db powa sui client), rimuovere il pacchetto “vecchio”

apt remove postgresql-16-powa

aggiornare il pacchetto all’ultima release


apt install -y postgresql-server-dev-16 build-essential git

scarica e compila il codice

cd /tmp
git clone https://github.com/powa-team/powa-archivist.git
cd powa-archivist

# Compila e installa
make
make install

ricollegarsi al db e reinstallare le estensioni

\c powa   -- o il db che usi
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE EXTENSION powa;   -- ora installa la 5.x
SELECT extversion FROM pg_extension WHERE extname = 'powa';
-- deve rispondere: 5.1.x

esegui un controllo sul masterserver se tutto arriva in maniera coorretta

journalctl -u powa-collector -f | more
Apr 29 13:30:44 powaserver powa-collector[17130]: 2026-04-29 13:30:44,091 - DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 0.5 ms
Apr 29 13:30:44 powaserver powa-collector[17130]: SELECT 1
Apr 29 13:30:54 powaserver powa-collector[17130]: 2026-04-29 13:30:54,102 - DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 0.59 ms
Apr 29 13:30:54 powaserver powa-collector[17130]: SELECT 1
Apr 29 13:31:04 powaserver powa-collector[17130]: 2026-04-29 13:31:04,113 - DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 0.47 ms
Apr 29 13:31:04 powaserver powa-collector[17130]: SELECT 1
Apr 29 13:31:14 powaserver powa-collector[17130]: 2026-04-29 13:31:14,124 - DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 0.47 ms
Apr 29 13:31:14 powaserver powa-collector[17130]: SELECT 1
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,849 10.10.0.213:5432 DEBUG : Connecting on repository...
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,875 10.10.0.213:5432 DEBUG : Retrieving extension schemas...
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,878 10.10.0.213:5432 DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 2.93 ms
Apr 29 13:31:20 powaserver powa-collector[17130]: SELECT extname, quote_ident(nspname) AS nsp
Apr 29 13:31:20 powaserver powa-collector[17130]:         FROM pg_catalog.pg_extension e
Apr 29 13:31:20 powaserver powa-collector[17130]:         JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,878 10.10.0.213:5432 DEBUG : extension schemas: {'plpgsql': 'pg_catalog', 'pg_stat_statements': 'public', 'btree_gist': 'public', 'powa'
: 'public', 'pg_qualstats': 'public', 'pg_stat_kcache': 'public', 'pg_wait_sampling': 'public', 'pg_track_settings': 'public', 'hypopg': 'public'}
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,879 10.10.0.213:5432 DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 0.75 ms
Apr 29 13:31:20 powaserver powa-collector[17130]: SELECT
Apr 29 13:31:20 powaserver powa-collector[17130]:         regexp_split_to_array(extversion, E'\\.'),
Apr 29 13:31:20 powaserver powa-collector[17130]:         extversion
Apr 29 13:31:20 powaserver powa-collector[17130]:         FROM pg_catalog.pg_extension
Apr 29 13:31:20 powaserver powa-collector[17130]:         WHERE extname = 'powa'
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,879 10.10.0.213:5432 DEBUG : Setting secure search_path
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,880 10.10.0.213:5432 DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 0.2 ms
Apr 29 13:31:20 powaserver powa-collector[17130]: SET search_path TO 'pg_catalog'
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,880 10.10.0.213:5432 DEBUG : Connected.
Apr 29 13:31:20 powaserver powa-collector[17130]: 2026-04-29 13:31:20,880 10.10.0.213:5432 DEBUG : query on user=powa password=xxx dbname=powa host=localhost port=5432: 0.49 ms

A questo punto creare il file unit per powa-collector

cat > /etc/systemd/system/powa-collector.service << 'EOF'
[Unit]
Description=PoWA Collector - PostgreSQL Workload Analyzer
Documentation=https://powa.readthedocs.io
After=network.target postgresql.service
Wants=postgresql.service

[Service]
Type=simple
User=postgres
Group=postgres

# Trova automaticamente il binario ovunque sia installato
ExecStart=/usr/bin/powa-collector

# Ricarica config con: systemctl kill -s HUP powa-collector
ExecReload=/bin/kill -HUP $MAINPID

Restart=on-failure
RestartSec=10s

# Log su journald
StandardOutput=journal
StandardError=journal
SyslogIdentifier=powa-collector

[Install]
WantedBy=multi-user.target
EOF

e per lanciarlo

systemctl daemon-reload
systemctl enable powa-collector
systemctl start powa-collector
systemctl status powa-collector


configurate anche powa-wec con il file /etc/powa-web.con

Spero di non aver dimenticato nulla: buon PoWA !

Ho abbandonato l’hosting – Hardening

Per migliorare la sicurezza del mio vps, ho pensato che non potevo lasciarlo solo con un “semplice” firewall e basta.

Per prima cosa, visto che ho installato WireGuard sia sul server VPS che sul mio client, ho spostato tutte le comnessioni da “any” per le interfacce di ftp, al wg0, linterfaccia di wireGuard

[ 7] 21/tcp on wg0              ALLOW IN    10.20.0.0/24              
[ 8] 50000:50999/tcp on wg0     ALLOW IN    10.20.0.0/24    

Facendo un controolo sul failban incorporato nel docker del mail sercve faccio una pessima scoperta (ma prevedibile)

docker exec -it mailserver iptables -L -n
OCI runtime exec failed: exec failed: unable to start container process: exec: "iptables": executable file not found in $PATH

Quindi il failban inserito nel container, non solo non fa il suo lavoro, ma come potrebbe farlo se modifica il caneuto del firewall all0interno di un container che non contiene iptables ?

Per tutelare la macchina allora dovrei installare failban a livello di SO, ma girovagando zmi sono imbattuto in CrowdSec che è un IDS/IPS ed anche WAF: perfetto !

In soldoni CrowdSec analizza i log e verifica se sono attacchi o meno.

per installarlo

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | bash
apt install -y crowdsec crowdsec-firewall-bouncer-iptables

Per prima cosa, allora ho disabilitato all’interno del container del mail server fail2ban

ENABLE_FAIL2BAN=0

e poi

docker compose restart mailserver

e cosiderato che CS deve leggere i log, ho modificato dove scrivere i log in modo che possano essere analizzati

il reverse proxy in

  - /var/log/docker/nginx:/var/log/nginx

ed il mail server in

- /var/log/docker/mail:/var/log/mail

ho lasciato il “resto” nei path preimpostati.

I log sono definiti nel file

/etc/crowdsec/acquis.d/prod.yaml

ed il cui contenuto deve essere il seguente

filenames:
  - /var/log/auth.log
  - /var/log/nginx/*.log
  - /var/log/mail.log
  - /var/log/docker/mail/*.log
  - /var/log/docker/nginx/*.log

labels:
  type: syslog

riavviamo

systemctl restart crowdsec

e vediamo che succede

cscli metrics
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Acquisition Metrics                                                                                                              │
├────────────────────────────────────────┬────────────┬──────────────┬────────────────┬────────────────────────┬───────────────────┤
│ Source                                 │ Lines read │ Lines parsed │ Lines unparsed │ Lines poured to bucket │ Lines whitelisted │
├────────────────────────────────────────┼────────────┼──────────────┼────────────────┼────────────────────────┼───────────────────┤
│ file:/var/log/auth.log                 │ 209        │ 179          │ 30             │ 581                    │ -                 │
│ file:/var/log/docker/mail/clamav.log   │ 40         │ -            │ 40             │ -                      │ -                 │
│ file:/var/log/docker/mail/fail2ban.log │ 45         │ -            │ 45             │ -                      │ -                 │
│ file:/var/log/docker/mail/mail.log     │ 27         │ 2            │ 25             │ 1                      │ -                 │
│ file:/var/log/docker/nginx/access.log  │ 324        │ -            │ 324            │ -                      │ -                 │
│ file:/var/log/docker/nginx/error.log   │ 1          │ -            │ 1              │ -                      │ -                 │
│ file:/var/log/kern.log                 │ 61         │ -            │ 61             │ -                      │ -                 │
│ file:/var/log/syslog                   │ 100        │ -            │ 100            │ -                      │ -                 │
╰────────────────────────────────────────┴────────────┴──────────────┴────────────────┴────────────────────────┴───────────────────╯

bene: stiamo loggndo il traffico.

Ulteriore conferma da

cscli bouncers list
─────────────────────────────────────────────────────────────────────────────────
 Name                 IP Address  Valid  Last API pull  Type  Version  Auth Type 
─────────────────────────────────────────────────────────────────────────────────
 cs-firewall-bouncer              ✔️                                   api-key   
─────────────────────────────────────────────────────────────────────────────────

CrowdSec blocca gli ip PRIMA del loro arrivo sul container

AzioneComando
IP bannaticscli decisions list
Sblocca IPcscli decisions delete --ip X.X.X.X
Metrichecscli metrics

per verificare che l’estensioni del fireall siamo installate

il comando rowdsec-firewall-bouncer -version deve rispondere

rowdsec-firewall-bouncer -version
version: v0.0.34-debian-pragmatic-amd64-4144555453620958398aee64253dfd90bbc1f698
BuildDate: 2025-08-04_10:04:33
GoVersion: 1.24.5
Platform: linux

e con iptables -S | grep CROWDSEC abbiamo la conferma che il iptables hasubito un’aggiunta

-N CROWDSEC_CHAIN
-A INPUT -j CROWDSEC_CHAIN
-A CROWDSEC_CHAIN -m set --match-set crowdsec-blacklists-1 src -m comment --comment "CrowdSec: CAPI" -j DROP
-A CROWDSEC_CHAIN -m set --match-set crowdsec-blacklists-0 src -m comment --comment "CrowdSec: crowdsec" -j DROP

Il database gestito da CrowdSec non va bene in sqlite (secondo la mia testa) ed alloa utilizzo postresql in container il cui file docker-compose.yaml in /opt/postgis è

services:
  postgres:
    container_name: postgiis_container
    image: postgis/postgis:16-3.5
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: defaultpassword
      PGDATA: /var/lib/postgresql/data
      POSTGRES_LOGGING_COLLECTOR: "off"
    volumes:
      - ./data:/var/lib/postgresql/data
      - ./postgresql.conf:/etc/postgresql/postgresql.conf
      - ./pg_hba.conf:/etc/postgresql/pg_hba.conf
    ports:
      - "5432:5432"
    restart: always
    command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf",
               "-c", "hba_file=/etc/postgresql/pg_hba.conf",]
volumes:
  data:
    driver: local
  logs:
    driver: local
definire db ed utente di postgresql
sudo -u postgres psql
CREATE DATABASE crowdsec;

CREATE USER crowdsec WITH PASSWORD 'PASSWORD_FORTE';
GRANT ALL PRIVILEGES ON DATABASE crowdsec TO crowdsec;
GRANT USAGE, CREATE ON SCHEMA public TO crowdsec;
GRANT CONNECT ON DATABASE crowdsec TO crowdsec;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO crowdsec;
ALTER SCHEMA public OWNER TO crowdsec;

CREATE USER crowdsec_ro WITH PASSWORD 'PASSWORD_RO';
GRANT CONNECT ON DATABASE crowdsec TO crowdsec_ro;
GRANT USAGE ON SCHEMA public TO crowdsec_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO crowdsec_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
\q

l’utente crowdsec_ro potrebbe serivire per leggere le tabelle di postgresql.

ho impostato postgres_logging_collector a off in quanto mi dava problemi per la reazione del file di log, ma essendo un database solo per CrowdSec non mi ci ho pensato due volte as togliere ilfile di log.

modifichiamo il file /etc/crowdsec/config.yaml per togliere sqllite ed instanziare postgresql: da

db_config:
  type: sqlite
  db_path: /var/lib/crowdsec/data/crowdsec.db

a

db_config:
  type: postgresql
  host: 127.0.0.1
  port: 5432
  user: crowdsec
  password: PASSWORD_FORTE
  db_name: crowdsec
  sslmode: disable

a questo punto

systemctl restart crowdsec

eseguendo

sudo -u root psql crowdsec -c "\dt"

devono apparire le seguenti tabelle

crowdsec=> \dt
                   List of relations
 Schema |            Name            | Type  |  Owner   
--------+----------------------------+-------+----------
 public | alerts                     | table | crowdsec
 public | allow_list_allowlist_items | table | crowdsec
 public | allow_list_items           | table | crowdsec
 public | allow_lists                | table | crowdsec
 public | bouncers                   | table | crowdsec
 public | config_items               | table | crowdsec
 public | decisions                  | table | crowdsec
 public | events                     | table | crowdsec
 public | locks                      | table | crowdsec
 public | machines                   | table | crowdsec
 public | meta                       | table | crowdsec
 public | metrics                    | table | crowdsec
(12 rows)

allora tutto bene!

Siccome abbiamo cambiato le carte in tavola, crowdsec potrebbe non essere contento del cambio.

Potrebbe essere necessario eseguire

rm -f /etc/crowdsec/local_api_credentials.yaml

che non fa altro che cancellare il vecchio riferimento e per ricrearlo bisogna eseguire

cscli machines add localhost --auto

il cui output deve essere

Machine 'localhost' successfully added

ed allora far ripartire crowdsec

systemctl restart crowdsec

e vedere il risultato da

cscli alerts list
cscli alerts inspect "alert da sopra"
cscli decisions list

Ho abbandonato l’hosting – ftps server

L’ultimo pezzo che ho configurato è il server ftp con l’estensione S: FTPS.

ho installato il solito vsftp con i

sudo apt install vsftp

ed ho impostato la parte di cifratura generANDO IN PRIMIS UN CERTIFICATO SELF-SIGNED

sudo openssl req -x509 -days 365 -newkey rsa:2048 -nodes -keyout /etc/vsftpd.pem -out /etc/vsftpd.pem

Una volta creato bisogna dire a vsftpd di utilizzarlo in aggiungendo al /etc/vsftpd.conf queste righe


ssl_enable=YES

allow_anon_ssl=NO
force_local_data_ssl=NO
force_local_logins_ssl=NO
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=NO
ssl_ciphers=HIGH

rsa_cert_file=/etc/vsftpd.pem
rsa_private_key_file=/etc/vsftpd.pem


# define port range for passive mode connections
pasv_min_port=50000
pasv_max_port=50999

e far ripartire vsftpd con un

systemctl restart vsftpd.service

Sono state inserite anche le porte che il server FTP deve indicare al client per il collegamento passivo, senza quell’indicazione si sarebbero dovute aprire troppe porte.


pasv_enable=YES
pasv_addr_resolve=YES
pasv_address=www.miodominio.it

In questo modo svincolo l’associazione ip – nome

Per la sicurezza sono passato alla VPN con wireguard ed ho rimosso /cambiato l’ultimo blocco in

pasv_enable=YES
pasv_address=10.20.0.1

Ho abbandonato l’hosting – la parte del mailserver

Non potevo evitare la mail del mio dominio.

Qui arriva la parte più complessa. Iniziamo dal file di configurazione del mail server: mailserver.env

# Generale
ONE_DIR=1
PERMIT_DOCKER=network
LOG_LEVEL=info

# Sicurezza
ENABLE_FAIL2BAN=1
ENABLE_SPAMASSASSIN=1
ENABLE_CLAMAV=1

# SMTP
ENABLE_POSTGREY=1
POSTGREY_DELAY=300

# SASL
ENABLE_SASLAUTHD=1

# Disabilita relay aperto
POSTFIX_MESSAGE_SIZE_LIMIT=52428800

POSTFIX_MYORIGIN=midominio.it
POSTFIX_MASQUERADE_DOMAINS=miodominio.it altromiodominio.it
POSTFIX_MASQUERADE_EXCEPTIONS=root

ENABLE_SRS=1
ENABLE_IMAP=1

# TLS
IMAP_TLS_SECURITY_LEVEL=encrypt
SSL_TYPE=letsencrypt

# Timezone (già che ci siamo)
TZ=Europe/Rome

il contaier ha tutto quello che serve: SMTP, IMAP, LDAP, Antispam, Antivirus, etc

di seguito i valori da inserire nel DNS GiDiNet: (IP_CONTABO è l’IP pubblico del server/dominio)

  • MX
miodominio.it.   MX 10 mail.miodominio.it.
  • A
mail.miodominio.it → IP_CONTABO
  • SPF
v=spf1 ip4:IP_CONTABO -all
  • DKIM
docker exec mailserver setup config dkim

Aggiungi la TXT generata.

  • DMARC
_dmarc.miodominio.it TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@miodominio.it"

il file della chiave DKIM si trova

more /tmp/docker-mailserver/opendkim/keys/miodominio.it/mail.txt

e va riportatat nel mail server con i soli apici “esterni”

una volta settata la parte critica del mail server, iniziamo ad aggiungere utenti:

docker exec -it mailserver setup email add info@miodominio.it PASSWORD
docker exec -it mailserver setup email add info@altromiodominio.it PASSWORD

Hai letto bene: con la stessa configurazione posso gestire più dominii: basta duplicare le informazioni sul DNS.

bisogna abilitare l’ingresso delle porte 25, 465 e 587

sudo ufw allow 25/tcp
sudo ufw allow 465/tcp
sudo ufw allow 587/tcp

Posso vedere gli utenti registrati con

docker exec -it mailserver setup email list

e per inviare mail di test:

docker exec -it mailserver swaks \
  --to test@gmail.com \
  --from info@miodominio.it \
  --server localhost \
  --data "Subject: Test\n\nTest SPF DKIM"

L’ultimo pezzo è la webmail: per vedere ovunque le mail.

Il client è molto semplice da installare, ma bisogna cercare la password di admin per la configurazione

docker exec snappymail cat  /var/lib/snappymail/_data_/_default_/admin_password.txt
+pincopallo

a questo punto con il browser puntare a

https://webmail.miodominio.it/?admin

e dopo aver digitato user e password nella voce Domains sulla sinistra, selezionare uno per volta i domini ed al posto di localhost indicare mailserver: una volta modificato sia IMAP che SMTP cliccare su Test. Se tutto è andato bene, allora dovreste avere le due tab in verde. Cliccare su salva e dopo eseguire le stesse operazioni sull’altro dominio.

E’ imposrtante che prima sia stato inserito almeno un utente per fare i test di connessione.

Possiamo anche contattare il nostro mail server con i normali client di posta elettronica basandoci su questi parametri

Parametri IMAP
Server IMAPwebmail.miodominio.it
Porta993
SicurezzaSSL/TLS
Usernameuser@miodominio.it
Passwordquella della mailbox
SMTP
Servermail.miodominio.it
Porta587
STARTTLS
Auth

Ho abbandonato l’hosting – la parte del WordPress e backup

finite le foto, sono passato a configurare WordPress. Mi ero fastto un backup del vecchio sito che è quello su cui stai leggendo l’articolo) e volevo ripristinarlo.

Il plugin utilizzato è fenomenale ! Salva tutto : All-in-One WP Migration and Backup

Dopo aver eseguito la configurazione del db (come per piwigo) ho detto al plgin di caricare il file di backup, ma la dimensione del file superava abbondantemente il limite di caricamento di un file via php.

Dopo una breve ricerca, ho trovato la soluzione (solo per il container wp1, ma fa scuola): inserire dei parametri “aggiuntivi” al container

      - ./php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini

il cui contenuto è

upload_max_filesize = 512M
post_max_size = 512M
memory_limit = 512M
max_execution_time = 300
max_input_time = 300

per far ricaricare la configurazione basta un

per verificare

docker restart wordpress1
docker exec -it wordpress1 php -i | grep upload_max_filesize
docker exec -it wordpress1 php -i | grep post_max_size

il cui output è

upload_max_filesize => 512M => 512M
post_max_size => 512M => 512M

in questo modo si risparmiano i 67$ per caricare il file e rendiamo molto più “smart” il nostro sistema. Nulla e nessuno ci vieta di eliminarlo in quanto serve solo per il caricamento del backup.

In definitiva, anche se abbiamo eseguito una nuova configurazione di WP., i dati di accesso sono i “veccji”.

Bisogna abilitare nel file di nginx il primo wp, generare il certificato ed infine abilitare il passaggio da http a https del sito con operazioni già viste precedentemente.

Tutto bello finora, ma se dovessi fare io qualche errore oppure voglio fare il restore di qualcosa. Si, è vero i dati non sono modificati in maniera fobica, ma un backup ?

Contabo per 0,75€ al mese (mi sembra) ti offre il backup, ma meglio prevenire ed allora cerco un modo.

Lo script utilizzato per il backup è

#!/bin/bash
BACKUP_DIR="/opt/docker/backups/$(date +%F)"
mkdir -p "$BACKUP_DIR"

# DB dump
docker exec mariadb sh -c \
  "mariadb-dump --all-databases -u root -p'$MYSQL_ROOT_PASSWORD' --single-transaction" \
  > "$BACKUP_DIR/db_$(date +%F).sql"
gzip "$BACKUP_DIR/db_$(date +%F).sql"

# WordPress and Piwigo files
tar czf "$BACKUP_DIR/wp1_files.tar.gz" /opt/docker/wp1/html
tar czf "$BACKUP_DIR/wp2_files.tar.gz" /opt/docker/wp2/html
tar czf "$BACKUP_DIR/wp3_files.tar.gz" /opt/docker/wp3/html
tar czf "$BACKUP_DIR/piwigo_gallery.tar.gz" /opt/docker/piwigo/gallery

echo "Backup completato in $BACKUP_DIR"

dove viene eseguito il backup di tutto il db e la parte

A casa ho un disco esterno, ma il server si trova (penso) in Germania quindi dobbiamo usare la rete per raggiungere il server: wireguard.

sia sul server che sul mio pc lancio un

sudo apt -y update
sudo apt -y install wireguard

Una volta installato, bisogna generare le chiavi per il saltaggio remoto

wg genkey | tee private.key | wg pubkey > public.key

sul server contabo , bisogna generare il file /etc/wireguard/wg0.conf

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = <PRIVATE_KEY_CONTABO>

[Peer]
PublicKey = <PUBLIC_KEY_MINI_CED>
AllowedIPs = 10.10.10.2/32

e sul cliente remoto sempre il file /etc/wireguard/wg0.conf ma con il contenuto

[Interface]
Address = 10.10.10.2/24
PrivateKey = <PRIVATE_KEY_MINI_CED>

[Peer]
PublicKey = <PUBLIC_KEY_CONTABO>
Endpoint = IP_CONTABO:51820
AllowedIPs = 10.10.10.1/32
PersistentKeepalive = 25

abilitare il servizio su entrambe le macchine

systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0

e fare un banale test dal server Contabo

ping 10.10.10.2

ma il ping non funziona (quasi sicuramente) in quanto la porta udp 51820 non è abilitata a passare. Bisogna eseguire

sudo ufw allow 51820/udp

e tutto andrà a posto.

Abilitare l’accesso SENZA password per ssh dal server Contabo:

ssh-keygen
ssh-copy-id backup@10.10.10.2

naturalmente l’utente sulla macchina remota

A questo punto lo script di backup diventa

#!/bin/bash
set -e

DATE=$(date +%G-W%V)
BACKUP_DIR="/opt/backup/$DATE"
REMOTE="backup@10.10.10.2:/data/contabo"

mkdir -p "$BACKUP_DIR"

echo "▶ DB dump"
docker exec mariadb \
  sh -c 'mariadb-dump --all-databases -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction' \
  | gzip > "$BACKUP_DIR/db.sql.gz"

echo "▶ Files"
tar czf "$BACKUP_DIR/wp1.tar.gz" /opt/docker/wp1/html/wp-content
tar czf "$BACKUP_DIR/wp2.tar.gz" /opt/docker/wp2/html/wp-content
tar czf "$BACKUP_DIR/piwigo.tar.gz" /opt/docker/piwigo/gallery

echo "▶ Sync verso mini-CED"
rsync -avz --delete "$BACKUP_DIR/" "$REMOTE/$DATE/"

echo "✔ Backup completato"

aggiungere nel crontab

0 3 * * 0 /opt/backup/weekly_backup.sh >> /var/log/backup.log 2>&1

e possiamo anche aggiungere nello script

find /opt/backup -maxdepth 1 -type d -mtime +56 -exec rm -rf {} \;

per la rotazione dei backup.

Per il restore dei dati:

rsync -av backup@10.10.10.2:/data/contabo/2026-W05/ /restore/
gunzip db.sql.gz
docker exec -i mariadb mysql < db.sql

a pensarci bene, non mi piace la soluzione: posso accedere dalla periferia verso il computer..

Facciamo che è il mio computer ad accedere il server su Contabo.

ssh-copy-id admin@10.10.10.1

e poi nel crontab del mio pc

0 4 * * 0 /usr/bin/rsync  -e "ssh -p 9999" -avzP admin@10.20.0.1:backup/ data/contabo/

Ho abbandonato l’hosting – la parte di DNS e base container

Bhe.. dopo aver avuto il server come mi posso collegare e cosa posso farci?

L’appetito vien mangiando, si dice, ed allora mi sono messo alla ricerca di un fornitore di “domain registration service” e la scelta è caduta su GiDiNet, aziendqa italiano.

L’idea di base era installare solo wordpress e piwigo per le foto, ma poi ho esagerato:

Internet
|
v
[ NGINX ] <-- unico che espone 80/443 | 
+--> wordpress1
+--> wordpress2
+--> wordpress3
+--> piwigo
+--> webmail
+--> certbot

ma per fare questo non potevo pensare a “distribuzioni” dei pacchetti .deb, ma mi sono affidato ai container e di seguito il docker-compose.yml finale.

services:

# -------------------
# MariaDB centralizzata
# -------------------
db:
image: mariadb:10.6
container_name: mariadb
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: password
TZ: Europe/Rome
command:
- --innodb-buffer-pool-size=512M
- --max-connections=100
volumes:
- ./db:/var/lib/mysql

# -------------------
# Piwigo
# -------------------
piwigo:
image: linuxserver/piwigo
container_name: piwigo
restart: unless-stopped
depends_on:
- db
environment:
PUID: 1000
PGID: 1000
volumes:
- ./piwigo/config:/config
- ./piwigo/gallery:/gallery
ports:
- "127.0.0.1:8081:80" # esposto solo internamente

# -------------------
# WordPress 1
# -------------------
wordpress1:
image: wordpress:php8.2-apache
container_name: wordpress1
restart: unless-stopped
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wp1
WORDPRESS_DB_USER: wp1
WORDPRESS_DB_PASSWORD: password
PHP_MEMORY_LIMIT: 256M
PHP_MAX_EXECUTION_TIME: 300
PHP_MAX_INPUT_TIME: 300
TZ: Europe/Rome
volumes:
- ./wp1/html:/var/www/html
- ./php/wp1/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
ports:
- "127.0.0.1:8082:80"

# -------------------
# WordPress 2
# -------------------
wordpress2:
image: wordpress:php8.2-apache
container_name: wordpress2
restart: unless-stopped
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wp2
WORDPRESS_DB_USER: wp2
WORDPRESS_DB_PASSWORD: password
PHP_MEMORY_LIMIT: 256M
PHP_MAX_EXECUTION_TIME: 120
TZ: Europe/Rome
volumes:
- ./wp2/html:/var/www/html
ports:
- "127.0.0.1:8083:80"

# -------------------
# WordPress 3
# -------------------
wordpress3:
image: wordpress:php8.2-apache
container_name: wordpress3
restart: unless-stopped
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wp3
WORDPRESS_DB_USER: wp3
WORDPRESS_DB_PASSWORD: password
PHP_MEMORY_LIMIT: 256M
PHP_UPLOAD_MAX_FILESIZE: 512M
PHP_POST_MAX_SIZE: 256M
PHP_MAX_EXECUTION_TIME: 300
PHP_MAX_INPUT_TIME: 300
TZ: Europe/Rome
volumes:
- ./wp3/html:/var/www/html
ports:
- "127.0.0.1:8084:80"

# -------------------
# Nginx reverse proxy
# -------------------
nginx:
image: nginx:stable
container_name: nginx
restart: unless-stopped
depends_on:
- piwigo
- wordpress1
- wordpress2
- wordpress3
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./certbot/www:/var/www/certbot
- ./certbot/certificates:/etc/letsencrypt
- ./php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
environment:
TZ: Europe/Rome
ports:
- "80:80"
- "443:443"

# -------------------
# mailServer
# -------------------
mailserver:
image: mailserver/docker-mailserver:latest
container_name: mailserver
hostname: mail
domainname: miodominio.it
env_file: mailserver.env

environment:
TZ: Europe/Rome
ports:
- "25:25"
- "465:465"
- "587:587"
- "993:993"

volumes:
- ./maildata:/var/mail
- ./mailstate:/var/mail-state
- ./config:/tmp/docker-mailserver
- ./certbot/certificates:/etc/letsencrypt
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ./logs:/var/log/mail

cap_add:
- NET_ADMIN
- SYS_PTRACE

# -------------------
# Webmail
# -------------------
snappymail:
image: djmaze/snappymail
container_name: snappymail
restart: unless-stopped
volumes:
- ./snappymail/data:/snappymail/data

L’idea era quella di utilizzare nginx come reverse proxy come unico “oggetto” esposto su internet ed utilizzare let’s encript come fornitore dei certificati oper l’https.

Peocediamo con ordine.Sulla pagine di gestione di GiDiNet ho impostato (per iniziare) tre FQDN:

www.miodominio.it
photo.midominio.it
miodominio.it

e poi la vera configurazione del reverse proxy

## ================================================
## HTTP redirect + ACME challenge
## ================================================
server {
    listen 80;
    server_name photo.miodominio.it ;

    # ACME challenge per Certbot
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # Redirect tutto il resto su HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

## ================================================
## Piwigo HTTPS
## ================================================
server {
    listen 443 ssl;
    server_name photo.miodominio.it;

    ssl_certificate /etc/letsencrypt/live/photo.miodominio.it/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/photo.miodominio.it/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://piwigo:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Per la generazione del certificato PRIMA di passare alla 443, bisogna esporre il sito sulla porta 80 in modo che let’s encript possa generare correttamente il certificato: in definitiva bisogna commentare le quattro righe dei certificati ed abilitare la porta 80, modificando la porta 443 ssl in 80 nella sezione del server.

Per la generazione del certificato bisogna lanciare questo comando

docker run --rm -v /opt/docker/certbot/www:/var/www/certbot -v /opt/docker/certbot/certificates:/etc/letsencrypt certbot/certbot certonly \
  --webroot -w /var/www/certbot -d photo.miodominio.it  \
  --email admin@miodominio.it --agree-tos --no-eff-email

per evitare errori, bisogna creare a mano un file tramite

mkdir -p /opt/docker/certbot/certificates
openssl dhparam -out /opt/docker/certbot/certificates/ssl-dhparams.pem 2048

come parametro possiamo dare anche la chiava a 4096 bit: ci vorrà solo un tempo maggiore per la generazione, ma la sicurezza aumenta.

Contabo non aggiunge lo swap alla macchina: non fa male..

fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

Finora tutto a posto: ora lanciamo piwigi per vedere la configurazione, ma prima dobbiamo creare il database e l’utente per accedere al db.

docker exec -it mariadb mysql -u root -p

Inserisci la MYSQL_ROOT_PASSWORD, poi:

CREATE DATABASE piwigo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'piwigo'@'%' IDENTIFIED BY 'piwigo_strong_password';
GRANT ALL PRIVILEGES ON piwigo.* TO 'piwigo'@'%';
FLUSH PRIVILEGES;
EXIT;

A questo punto inserire i dati dopo aver lanciato dal tuo browser il fatidico

https://photo.miodominio.it

bisogna solo inserire le credenziali passate durante la creazione del db.

Vale la pena ricordare che il container di piwigo è nella lan degli altri conainer, quindi il nome della macchina che espone il db è quello della configurazione del docker: db (poca fantasia)

Ho abbandonato l’hosting – la base

Effettivamente è il passo successivo e molto più responsabile dell’hosting: affittare un VPS per fare quello che voglio con un server esposto su internet.

Ho finito il contratto con il vecchio provider e mi sono messo alla ricerca di un server che fosse economico e (spero) affidabile e la scelta è caduta su Contabo, azienda tedesca, che con una sessantina di euro offrono una macchina Ubuntu con 4 core e 8 G di ram e 145GB di disco ssd: accettabile.

La prima cosa è stato di aggiornare il SO con il solito

apt -y update

apt -y full-upgrade

Siccome l’accesso è via root, ho creato un utente che potesse diventare root

adduser admin

sudo adduser admin sudo

Ho aggiunto il firewall ufw per bloccare accessi indesiderati abilitando per iniziare la porta 22 ed un altra per gli accessi ssh dall’esterno e verificato che all’inizio tutto funzioni, soprattutto l’accesso remoto via ssh sulla porta non standard.

vi /etc/ssh/sshd_config

Port 22
Port 9999

La porta 22 (sebbene commentata) è la porta standard del ssh

sudo ufw allow 22/tcp
sudo ufw allow 9999/tcp

Dopo aver verificato che tutto vada bene, ho eliminato dal fw l’accesso alla porta 22 verificando prima il numero della regola con

ufw status numbered
Status: active

 To                         Action      From
 --                         ------      ----

[ 1] 22/tcp ALLOW IN Anywhere

e poi eliminado la regola della porta 22/tcp

ufw delete 1

poi ho eliminato la possibilità di root di fare login. sempre nel file /etc/ssh/sshd_config

PermitRootLogin no

tramite

ssh-copy-id -i .ssh/id_rsa.pub admin@x.y.z.k

ho copiato la mia chiave pubblica per accedere senza password, ma solo con chiavi: la sicurezza non è mai troppa !!

E per la base credo sia tutto.