Script to instanciate in ~3min a Scaleway VM as Wireguard VPN with Unbound and Pi-hole, using cloud-init facilities. All these applications are dockerized, and the docker images are regularly pulled/updated by watchtower.
Scaleway is a french cloud provider with affordable costs.
Cheaper instances:
- STARDUST1-S (only available at fr-par-1 and nl-ams-1)
- AMP2-C1 (only available at fr-par-2; arm64)
.-~~~-. ┌───────────────────────────────────┐
.- ~ ~-( )_ _ │VPS │
/ Internet ~ -. │ ┌────────────┐ DNS │
| ◄──────┼─┤Unbound ◄───────┐ │
\ ▲ .' │ │(DNS solver)│ │ │
~- ._ ,..,.,.,., ,.│ -~ │ └────────────┘ │ │
' │ │ ┌──────┴─────┐ │
┌─────────────────┐ │ │ │Pi-Hole │ │
│ PC/Phone │ │ │ │(DNS filter)│ │
│ │ │ │ └──────▲─────┘ │
│ ┌─────────┐ │ │ │ ┌─────────┐ │ │
│ │Wireguard│ │ └────────┼──┤Wireguard├─────────┘ │
│ │ Client │ │ │ │ Server │ DNS │
│ │ │ ─┴──────────────────┴─ │ (VPN) │ │
│ │ ├──► VPN Tunnel ──► │ ┌────────────────┐ │
│ └─────────┘ ─┬──────────────────┬─ └─────────┘ │Watchtower │ │
│ │ │ │(images updater)│ │
└─────────────────┘ │ └────────────────┘ │
└───────────────────────────────────┘
- a Scaleway account
- scaleway-cli, using your account (
scw init
done) - jq
- perl
- tput
# AMP2-C1 available at fr-par-2 for testing, as cheap as STARDUST1-S, but arm64 instead of x86_64
vm_name=test zone=fr-par-2 type=AMP2-C1 ./create-scw-wireguard_pi-hole_unbound.sh
Note the parameters vm_name
, zone
and type
in the command-line.
Default values will be wireguard-vps
, nl-ams-1
and DEV1-S
otherwise.
NB: [ctrl]+[q]
to close the VM console attached to your terminal.
The script create-scw-wireguard_pi-hole_unbound.sh will:
- check the availability for this VM type
- create a VM
- attach the console to the running terminal
- run the cloud-init script.
The script basic_script.sh does exactly the same, but without any check or information display.
The cloud-init script pushed when creating the instance will:
- upgrade the OS
- install docker and other things (fail2ban, ...)
- generate a random password for root
- create a config file for Unbound
- configure and harden fail2ban using fail2ban-endlessh configuration
- clone endlessh in order to build the container image (cf. docker-compose.yml and
endlessh
's Dockerfile) - create and start an application stack composed of Unbound, Wireguard, Pi-Hole and Watchtower using docker-compose
- add several blocklists and will also whitelist several domains in Pi-Hole
- set a service to print the login and wireguard client information on the server console
- reboot the OS.
Very largely inspired/copied from IAmStoxe/wirehole, but modified and a bit simplified according to my needs.
The docker-compose stack relies on:
- alpinelinux/unbound (was previously mvance/unbound, but the latter was x86_64-only)
- linuxserver/docker-wireguard
- pihole/pihole
- containrrr/watchtower
- a built on-the-fly endlessh container to harden a bit fail2ban
Thanks to them for building these docker images, and of course to people involved in these projects.
for zone in fr-par-1 fr-par-2 fr-par-3 nl-ams-1 nl-ams-2 pl-waw-1 pl-waw-2; do
echo -e "\n== $zone ==\n"
scw instance server-type list --output=human zone=$zone
done
Open the console on your VM using the Scaleway console and restart the VM if you need to retrieve the root password and/or the wireguard information.
Alternative:
# List instances
scw instance server list zone=all
# Populate these variables
ZONE=<get value from instance list>
ID=<get value from instance list>
# Reboot instance
scw instance server reboot zone=$ZONE $ID
# Attach to the instance console ([CTRL]+[Q] to detach from console)
scw instance server console zone=$ZONE $ID
# List instances
scw instance server list zone=all
# Populate these variables
ZONE=<get value from instance list>
ID=<get value from instance list>
# Delete instance
scw instance server terminate with-ip=true with-block=true zone=$ZONE $ID
scw marketplace image list
Objective: route only Internet traffic to the VPN, but keep local network and DNS reachable.
Use WireGuard AllowedIPs Calculator to calculate the AllowedIPs
parameter.
Example:
[Interface]
PrivateKey = [REDACTED]
ListenPort = [REDACTED]
Address = 10.6.0.2/32
DNS = [REDACTED - LOCAL DNS IP]
[Peer]
PublicKey = [REDACTED]
AllowedIPs = 1.0.0.0/8, 2.0.0.0/7, 4.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/3, 96.0.0.0/4, 112.0.0.0/5, 120.0.0.0/6, 124.0.0.0/7, 126.0.0.0/8, 128.0.0.0/3, 160.0.0.0/5, 168.0.0.0/8, 169.0.0.0/9, 169.128.0.0/10, 169.192.0.0/11, 169.224.0.0/12, 169.240.0.0/13, 169.248.0.0/14, 169.252.0.0/15, 169.255.0.0/16, 170.0.0.0/7, 172.0.0.0/12, 172.32.0.0/11, 172.64.0.0/10, 172.128.0.0/9, 173.0.0.0/8, 174.0.0.0/7, 176.0.0.0/4, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, 200.0.0.0/5, 208.0.0.0/4, 224.0.0.0/4, ::/1, 8000::/2, c000::/3, e000::/4, f000::/5, f800::/6, fe00::/9, fec0::/10, ff00::/8
Endpoint = [REDACTED]:[REDACTED]
This AllowedIPs
excludes all local networks according to RFC1918.