Compare commits
8 Commits
78e1c37a9e
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9016605f0d | |||
| fc5d6209c1 | |||
| 4d8eb01e4c | |||
| 19d03ea624 | |||
| df78675198 | |||
| 8c56e2fc9f | |||
| e177300864 | |||
| 2b38127156 |
+11
-32
@@ -1,40 +1,19 @@
|
|||||||
# ============================================================
|
# Fuso orario IANA — obbligatorio
|
||||||
# WireGuard VPN — Configurazione locale
|
|
||||||
# Copia .env.example in .env e compila i valori richiesti.
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
# --- OBBLIGATORI ---
|
|
||||||
|
|
||||||
# IP pubblico o hostname DDNS del server (es. mio-host.duckdns.org)
|
|
||||||
WG_HOST=your-ddns-hostname.example.com
|
|
||||||
|
|
||||||
# Hash bcrypt della password per la web UI.
|
|
||||||
# Vedi README.md per come generarlo.
|
|
||||||
# ATTENZIONE: incolla il valore esatto, senza aggiungere $$ o escape.
|
|
||||||
PASSWORD_HASH=$2a$12$replacethiswithyourrealbcrypthash
|
|
||||||
|
|
||||||
# Fuso orario (formato IANA)
|
|
||||||
# Lista completa: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
|
||||||
TZ=Europe/Rome
|
TZ=Europe/Rome
|
||||||
|
|
||||||
# --- OPZIONALI (valori di default mostrati) ---
|
# UID/GID dell'utente host proprietario di wg-data/ (trova con: id -u && id -g)
|
||||||
|
# PUID=1000
|
||||||
|
# PGID=1000
|
||||||
|
|
||||||
# Porta UDP WireGuard
|
# Porte esposte sull'host (opzionali, default mostrati)
|
||||||
WG_PORT=51820
|
# WG_PORT=51820
|
||||||
|
# WG_UI_PORT=51821
|
||||||
|
|
||||||
# Porta TCP interfaccia web
|
# Limiti risorse container (opzionali — adatta alla RAM disponibile)
|
||||||
WG_UI_PORT=51821
|
# 512 MB → WG_MEM_LIMIT=96m
|
||||||
|
# 1 GB → WG_MEM_LIMIT=128m
|
||||||
# DNS inviati ai client
|
# 2 GB+ → WG_MEM_LIMIT=256m ← default
|
||||||
WG_DEFAULT_DNS=1.1.1.1,8.8.8.8
|
|
||||||
|
|
||||||
# --- OPZIONALI — Limiti risorse container (SBC) ---
|
|
||||||
# Decommenta e adatta alla RAM disponibile:
|
|
||||||
# 512 MB RAM → WG_MEM_LIMIT=128m
|
|
||||||
# 1 GB RAM → WG_MEM_LIMIT=256m (default)
|
|
||||||
# 2 GB+ RAM → WG_MEM_LIMIT=384m
|
|
||||||
# Tenere WG_MEMSWAP_LIMIT = WG_MEM_LIMIT per disabilitare lo swap del container.
|
# Tenere WG_MEMSWAP_LIMIT = WG_MEM_LIMIT per disabilitare lo swap del container.
|
||||||
# Su single-core (Pi Zero / Pi 1) impostare WG_CPUS=0.75.
|
|
||||||
# WG_MEM_LIMIT=256m
|
# WG_MEM_LIMIT=256m
|
||||||
# WG_MEMSWAP_LIMIT=256m
|
# WG_MEMSWAP_LIMIT=256m
|
||||||
# WG_CPUS=1.0
|
# WG_CPUS=1.0
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Variabili locali — contiene segreti, non committare mai
|
# Variabili locali — contiene segreti, non committare mai
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# Directory WireGuard — auto-generata dal container al primo avvio.
|
# Directory WireGuard — generata dal container al primo avvio.
|
||||||
# Contiene chiavi crittografiche private. Non committare mai.
|
# Contiene chiavi crittografiche private. Non committare mai.
|
||||||
wg-data/
|
wg-data/
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ A Docker Compose setup for a self-hosted WireGuard VPN server using [wg-easy](ht
|
|||||||
```bash
|
```bash
|
||||||
# First-time setup
|
# First-time setup
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Edit .env with WG_HOST, PASSWORD_HASH, TZ
|
# Edit .env: set TZ, optionally WG_PORT / WG_UI_PORT
|
||||||
|
|
||||||
# Generate bcrypt password hash (no extra dependencies)
|
|
||||||
docker run --rm -it ghcr.io/wg-easy/wg-easy wgpw 'YourPassword'
|
|
||||||
|
|
||||||
# Start the VPN
|
# Start the VPN
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@@ -28,20 +25,47 @@ docker compose logs -f wg-easy
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
All runtime configuration lives in `.env` (not committed). Required variables:
|
`.env` (not committed) controls only infrastructure-level settings:
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `WG_HOST` | Public IP or DDNS hostname clients connect to |
|
|
||||||
| `PASSWORD_HASH` | bcrypt hash of the web UI password (prefix `$2a$`, `$2b$`, or `$2y$`) |
|
|
||||||
| `TZ` | IANA timezone (e.g. `Europe/Rome`) |
|
| `TZ` | IANA timezone (e.g. `Europe/Rome`) |
|
||||||
|
| `WG_PORT` | UDP VPN port (default 51820) |
|
||||||
|
| `WG_UI_PORT` | Web UI port (default 51821) |
|
||||||
|
|
||||||
Optional: `WG_PORT` (default 51820/udp), `WG_UI_PORT` (default 51821/tcp), `WG_DEFAULT_DNS` (default `1.1.1.1,8.8.8.8`).
|
**v15+:** Host, password, and DNS are configured through the web UI wizard on first launch — not via environment variables.
|
||||||
|
|
||||||
## Important constraints
|
## Important constraints
|
||||||
|
|
||||||
- `wg-data/` is auto-generated by the container on first start and holds live WireGuard keys (`wg0.conf`, `wg0.json`). Never commit it.
|
- `wg-data/` is auto-generated by the container on first start and holds live WireGuard keys (`wg0.conf`, `wg-easy.db`). Never commit it. Permissions are set to `700` automatically by `wg-init`.
|
||||||
- `.env` is gitignored — it holds the password hash and hostname.
|
- `.env` is gitignored.
|
||||||
- The container requires `NET_ADMIN` and `SYS_MODULE` capabilities plus `net.ipv4.ip_forward=1` sysctl — these are already set in `docker-compose.yml`.
|
- The container requires `NET_ADMIN` and `SYS_MODULE` capabilities plus `net.ipv4.ip_forward=1` sysctl — these are already set in `docker-compose.yml`.
|
||||||
- The router must forward UDP port 51820 (or `WG_PORT`) to the server's local IP.
|
- The router must forward UDP port 51820 (or `WG_PORT`) to the server's local IP.
|
||||||
- `openssl passwd` does **not** support bcrypt — use the Docker method, `htpasswd`, or Python's `bcrypt` library to generate `PASSWORD_HASH`.
|
- `INSECURE=true` is set in `docker-compose.yml` to allow HTTP access on the local network.
|
||||||
|
|
||||||
|
## SBC / resource-constrained devices
|
||||||
|
|
||||||
|
Works on any Linux host with a 64-bit kernel and WireGuard support. Tested architectures:
|
||||||
|
|
||||||
|
| Hardware | Arch | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Raspberry Pi 5 | arm64 | kernel `6.12.x+rpt-rpi-2712` |
|
||||||
|
| Raspberry Pi 4 / 3B+ | arm64 / armv7 | 32-bit kernels need `armv7` image |
|
||||||
|
| Orange Pi, Rock Pi, Banana Pi | arm64 | depends on board BSP kernel |
|
||||||
|
| Generic x86\_64 server / VM | amd64 | standard Debian/Ubuntu/Fedora |
|
||||||
|
| Intel NUC / mini-PC | amd64 | same as above |
|
||||||
|
|
||||||
|
**Known issue — ip6tables modules not loaded at boot.**
|
||||||
|
Affects mostly SBC boards with custom BSP kernels, but can occur on any host where `ip6_tables` and `ip6table_nat` are not auto-loaded. Without them `wg-quick up wg0` fails and rolls back, leaving no `wg0` interface. Symptom: every API call returns `Command failed: wg show wg0 dump / No such device`.
|
||||||
|
|
||||||
|
The `wg-init` service handles this automatically: it runs `modprobe ip6_tables ip6table_nat` (with `SYS_MODULE` cap and `/lib/modules` bind-mounted read-only) before wg-easy starts. Failures are silenced (`|| true`) so the setup works on kernels where these modules are built-in or unavailable.
|
||||||
|
|
||||||
|
**Resource limits** (override via `.env`):
|
||||||
|
|
||||||
|
| Variable | Default | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `WG_MEM_LIMIT` | `256m` | Hard cap for the wg-easy container |
|
||||||
|
| `WG_MEMSWAP_LIMIT` | `256m` | Keep equal to `WG_MEM_LIMIT` to disable container swap |
|
||||||
|
| `WG_CPUS` | `1.0` | `0.75` on single-core boards (Pi Zero, Pi 1) |
|
||||||
|
|
||||||
|
The limit exists to prevent Node.js from slowly growing over long uptime and triggering the host OOM-killer (symptom: SSH becomes unreachable). Do not go below `96m` or the runtime OOM-kills on startup. See README §Dispositivi a risorse limitate for per-board guidance and swap configuration.
|
||||||
|
|||||||
@@ -27,60 +27,35 @@ cd vpn
|
|||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
Apri `.env` con un editor e compila i valori richiesti:
|
L'unica variabile obbligatoria è `TZ` (già precompilata con `Europe/Rome`). Le porte possono essere lasciate ai valori di default.
|
||||||
|
|
||||||
| Variabile | Descrizione | Obbligatoria |
|
| Variabile | Descrizione | Default |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `WG_HOST` | IP pubblico o hostname DDNS del server | Sì |
|
| `TZ` | Fuso orario (formato IANA) | `Europe/Rome` |
|
||||||
| `PASSWORD_HASH` | Hash della password per la UI web (vedi sotto) | Sì |
|
| `WG_PORT` | Porta UDP WireGuard | `51820` |
|
||||||
| `TZ` | Fuso orario (formato IANA, es. `Europe/Rome`) | Sì |
|
| `WG_UI_PORT` | Porta TCP interfaccia web | `51821` |
|
||||||
| `WG_PORT` | Porta UDP WireGuard (default: `51820`) | No |
|
|
||||||
| `WG_UI_PORT` | Porta TCP interfaccia web (default: `51821`) | No |
|
|
||||||
| `WG_DEFAULT_DNS` | DNS inviati ai client (default: `1.1.1.1,8.8.8.8`) | No |
|
|
||||||
|
|
||||||
### 3. Genera e imposta la password
|
> **Nota v15:** host VPN, password e DNS si configurano dalla web UI al primo avvio.
|
||||||
|
|
||||||
La password non si scrive in chiaro nel file `.env`: va convertita in un **hash** (una stringa cifrata che wg-easy usa per verificare il login senza conservare la password originale).
|
### 3. Apri la porta sul router
|
||||||
|
|
||||||
**Passo 1** — Scegli la password che vuoi usare per accedere all'interfaccia web, poi esegui questo comando sostituendo `LatuaPassword` con quella scelta:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --rm -it ghcr.io/wg-easy/wg-easy wgpw 'LatuaPassword'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Passo 2** — Il comando stampa una riga simile a questa:
|
|
||||||
|
|
||||||
```
|
|
||||||
PASSWORD_HASH='$2a$12$cDMCiMmFTuMOlT1E4BvxEO4CJfzMKSanRZSMqiE1234abcXYZ'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Passo 3** — Copia solo la parte dopo `PASSWORD_HASH=`, **senza** le virgolette singole. Nel file `.env` deve apparire così:
|
|
||||||
|
|
||||||
```
|
|
||||||
PASSWORD_HASH=$2a$12$cDMCiMmFTuMOlT1E4BvxEO4CJfzMKSanRZSMqiE1234abcXYZ
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Attenzione:** non aggiungere virgolette, spazi o altri caratteri intorno al valore. Copia e incolla esattamente quello che hai ottenuto.
|
|
||||||
|
|
||||||
### 4. Apri la porta sul router
|
|
||||||
|
|
||||||
Inoltra la porta **UDP 51820** (o quella scelta in `WG_PORT`) verso l'IP locale del server.
|
Inoltra la porta **UDP 51820** (o quella scelta in `WG_PORT`) verso l'IP locale del server.
|
||||||
|
|
||||||
### 5. Avvia il container
|
### 4. Avvia il container
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Al primo avvio la directory `wg-data/` viene creata automaticamente con tutte le chiavi crittografiche. Non è necessario modificarla manualmente.
|
Al primo avvio, un init container crea automaticamente `wg-data/` con permessi `700` prima che wg-easy scriva le chiavi private.
|
||||||
|
|
||||||
### 6. Accedi all'interfaccia web
|
### 5. Completa il setup dalla web UI
|
||||||
|
|
||||||
```
|
```
|
||||||
http://IP_DEL_TUO_SERVER:51821
|
http://IP_DEL_TUO_SERVER:51821
|
||||||
```
|
```
|
||||||
|
|
||||||
Da qui puoi aggiungere client, scaricare i file di configurazione e generare QR code per dispositivi mobili.
|
Al primo avvio appare un wizard che guida nella configurazione di password, hostname DDNS e DNS per i client. Dopo il setup puoi aggiungere client, scaricare configurazioni e generare QR code per dispositivi mobili.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -90,18 +65,46 @@ Da qui puoi aggiungere client, scaricare i file di configurazione e generare QR
|
|||||||
vpn/
|
vpn/
|
||||||
├── docker-compose.yml # Definizione del servizio
|
├── docker-compose.yml # Definizione del servizio
|
||||||
├── .env.example # Template variabili (committato)
|
├── .env.example # Template variabili (committato)
|
||||||
├── .env # Variabili locali con segreti (NON committato)
|
├── .env # Variabili locali (NON committato)
|
||||||
├── .gitignore
|
├── .gitignore
|
||||||
├── README.md
|
├── README.md
|
||||||
└── wg-data/ # Generato automaticamente dal container (NON committato)
|
└── wg-data/ # Generato dal container al primo avvio (ignorato da git)
|
||||||
├── wg0.conf # Configurazione WireGuard (chiavi reali)
|
├── wg0.conf # Configurazione WireGuard con chiavi private
|
||||||
└── wg0.json # Stato interno wg-easy (chiavi reali)
|
└── wg-easy.db # Database interno wg-easy
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dispositivi a risorse limitate (SBC)
|
||||||
|
|
||||||
|
Su board con poca RAM (≤ 1 GB) il processo Node.js di wg-easy può crescere nel tempo e far sì che il kernel OOM-killi altri processi di sistema — SSH compreso. Per evitarlo, decommenta e adatta questi valori nel tuo `.env`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Board con 512 MB RAM
|
||||||
|
WG_MEM_LIMIT=96m
|
||||||
|
WG_MEMSWAP_LIMIT=96m
|
||||||
|
|
||||||
|
# Board con 1 GB RAM
|
||||||
|
WG_MEM_LIMIT=128m
|
||||||
|
WG_MEMSWAP_LIMIT=128m
|
||||||
|
```
|
||||||
|
|
||||||
|
Tieni `WG_MEMSWAP_LIMIT` uguale a `WG_MEM_LIMIT` per disabilitare lo swap del container: quando il container raggiunge il limite viene riavviato da Docker invece di consumare swap di sistema.
|
||||||
|
|
||||||
|
Assicurati anche che lo swap dell'host sia almeno 512 MB. Su Raspberry Pi OS il default è 100 MB — per aumentarlo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dphys-swapfile swapoff
|
||||||
|
sudo sed -i 's/CONF_SWAPSIZE=100/CONF_SWAPSIZE=512/' /etc/dphys-swapfile
|
||||||
|
sudo dphys-swapfile setup && sudo dphys-swapfile swapon
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Aggiornare wg-easy
|
## Aggiornare wg-easy
|
||||||
|
|
||||||
|
Esegui mensilmente per ricevere patch di sicurezza:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose pull
|
docker compose pull
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@@ -111,6 +114,5 @@ docker compose up -d
|
|||||||
|
|
||||||
## Sicurezza
|
## Sicurezza
|
||||||
|
|
||||||
- `wg-data/` contiene chiavi crittografiche private. Non committarla mai su Git.
|
- `wg-data/` contiene chiavi crittografiche private ed è completamente ignorata da git. I permessi `700` vengono impostati automaticamente dall'init container al primo avvio.
|
||||||
- `.env` contiene la password (hash) e il tuo hostname. Non committarlo.
|
- `.env` contiene variabili locali specifiche del server. Non committarlo.
|
||||||
- Entrambi sono esclusi da `.gitignore`.
|
|
||||||
|
|||||||
+38
-17
@@ -1,25 +1,45 @@
|
|||||||
services:
|
services:
|
||||||
|
wg-init:
|
||||||
|
image: alpine:3.21
|
||||||
|
network_mode: none
|
||||||
|
cap_add:
|
||||||
|
- SYS_MODULE
|
||||||
|
environment:
|
||||||
|
PUID: "${PUID:-1000}"
|
||||||
|
PGID: "${PGID:-1000}"
|
||||||
|
volumes:
|
||||||
|
- ./wg-data:/data
|
||||||
|
- /lib/modules:/lib/modules:ro
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
modprobe ip6_tables 2>/dev/null || true
|
||||||
|
modprobe ip6table_nat 2>/dev/null || true
|
||||||
|
chown "${PUID}:${PGID}" /data
|
||||||
|
chmod 700 /data
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
wg-easy:
|
wg-easy:
|
||||||
image: ghcr.io/wg-easy/wg-easy:15.2.2 # Pinnato — aggiornare deliberatamente con compose pull
|
depends_on:
|
||||||
|
wg-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
image: ghcr.io/wg-easy/wg-easy:15
|
||||||
container_name: wg-easy
|
container_name: wg-easy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# --- Limiti risorse (SBC con 1 GB RAM) ---
|
|
||||||
# mem_limit = memswap_limit disabilita lo swap per il container:
|
|
||||||
# se supera 256 MB Docker lo riavvia pulito invece di mandare in OOM l'host.
|
|
||||||
mem_limit: "${WG_MEM_LIMIT:-256m}"
|
mem_limit: "${WG_MEM_LIMIT:-256m}"
|
||||||
memswap_limit: "${WG_MEMSWAP_LIMIT:-256m}"
|
memswap_limit: "${WG_MEMSWAP_LIMIT:-256m}"
|
||||||
cpus: "${WG_CPUS:-1.0}"
|
cpus: "${WG_CPUS:-1.0}"
|
||||||
|
|
||||||
# --- Rotazione log: evita che i log riempiano la SD card ---
|
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
# --- Health check: riavvio automatico se Node.js si blocca ---
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
# porta interna fissa; WG_UI_PORT controlla solo il mapping host
|
||||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:51821/"]
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:51821/"]
|
||||||
interval: 60s
|
interval: 60s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
@@ -27,27 +47,28 @@ services:
|
|||||||
start_period: 30s
|
start_period: 30s
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
PASSWORD_HASH: "${PASSWORD_HASH}"
|
|
||||||
TZ: "${TZ}"
|
TZ: "${TZ}"
|
||||||
WG_HOST: "${WG_HOST}"
|
INSECURE: "true"
|
||||||
WG_DEFAULT_DNS: "${WG_DEFAULT_DNS:-1.1.1.1,8.8.8.8}"
|
|
||||||
|
|
||||||
# Chiavi e configurazione WireGuard persistite fuori dal container
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./wg-data:/etc/wireguard
|
- ./wg-data:/etc/wireguard
|
||||||
|
|
||||||
# File temporanei in RAM invece che sulla SD card
|
read_only: true
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /tmp:size=32m,mode=1777
|
- /tmp:size=32m,mode=1777
|
||||||
|
- /run:size=8m
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- "${WG_PORT:-51820}:51820/udp" # Traffico VPN WireGuard
|
- "${WG_PORT:-51820}:51820/udp"
|
||||||
- "${WG_UI_PORT:-51821}:51821/tcp" # Interfaccia web di gestione
|
- "${WG_UI_PORT:-51821}:51821/tcp"
|
||||||
|
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN # Necessario per gestire le interfacce di rete (wg0) e le route
|
- NET_ADMIN
|
||||||
- SYS_MODULE # Necessario per caricare il modulo kernel wireguard
|
- SYS_MODULE
|
||||||
|
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
sysctls:
|
sysctls:
|
||||||
- net.ipv4.ip_forward=1 # Abilita il routing dei pacchetti IPv4 tra i client VPN e internet
|
- net.ipv4.ip_forward=1
|
||||||
- net.ipv6.conf.all.forwarding=1 # Abilita il routing IPv6 (richiesto anche se non si usa IPv6)
|
- net.ipv6.conf.all.forwarding=1
|
||||||
|
|||||||
Reference in New Issue
Block a user