├── .gitignore ├── README.md ├── TEST.md ├── assets ├── demo.gif ├── demo.tape └── scw-wireguard.png ├── basic_script.sh ├── cloud-init └── wireguard_pi-hole_unbound.sh └── create-scw-wireguard_pi-hole_unbound.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .project_id 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wireguard-vps 2 | 3 | ![screenshot](./assets/scw-wireguard.png) 4 | 5 | ## Goal 6 | 7 | Script to instanciate in ~3min a [Scaleway](https://www.scaleway.com/) VM as [Wireguard VPN](https://www.wireguard.com/) with [Unbound](https://nlnetlabs.nl/projects/unbound/about/) and [Pi-hole](https://github.com/pi-hole), using [cloud-init](https://cloudinit.readthedocs.io/en/latest/) facilities. 8 | All these applications are dockerized, and the docker images are regularly pulled/updated by [watchtower](https://github.com/containrrr/watchtower). 9 | 10 | [Scaleway](https://www.scaleway.com/) is a french cloud provider with affordable costs. 11 | 12 | Cheaper instances: 13 | 14 | - [STARDUST1-S](https://www.scaleway.com/en/stardust-instances/) (only available at fr-par-1 and nl-ams-1) 15 | - [AMP2-C1](https://www.scaleway.com/en/amp2-instances/) (only available at fr-par-2; arm64) 16 | 17 | ```text 18 | .-~~~-. ┌───────────────────────────────────┐ 19 | .- ~ ~-( )_ _ │VPS │ 20 | / Internet ~ -. │ ┌────────────┐ DNS │ 21 | | ◄──────┼─┤Unbound ◄───────┐ │ 22 | \ ▲ .' │ │(DNS solver)│ │ │ 23 | ~- ._ ,..,.,.,., ,.│ -~ │ └────────────┘ │ │ 24 | ' │ │ ┌──────┴─────┐ │ 25 | ┌─────────────────┐ │ │ │Pi-Hole │ │ 26 | │ PC/Phone │ │ │ │(DNS filter)│ │ 27 | │ │ │ │ └──────▲─────┘ │ 28 | │ ┌─────────┐ │ │ │ ┌─────────┐ │ │ 29 | │ │Wireguard│ │ └────────┼──┤Wireguard├─────────┘ │ 30 | │ │ Client │ │ │ │ Server │ DNS │ 31 | │ │ │ ─┴──────────────────┴─ │ (VPN) │ │ 32 | │ │ ├──► VPN Tunnel ──► │ ┌────────────────┐ │ 33 | │ └─────────┘ ─┬──────────────────┬─ └─────────┘ │Watchtower │ │ 34 | │ │ │ │(images updater)│ │ 35 | └─────────────────┘ │ └────────────────┘ │ 36 | └───────────────────────────────────┘ 37 | ``` 38 | 39 | ## How to create a wireguard + Unbound + PI-hole VM 40 | 41 | ### Prerequisites 42 | 43 | - a [Scaleway account](https://console.scaleway.com/register) 44 | - [scaleway-cli](https://github.com/scaleway/scaleway-cli), using your account (`scw init` done) 45 | - [jq](https://github.com/jqlang/jq) 46 | - [perl](https://www.perl.org/get.html) 47 | - [tput](https://manned.org/tput.1) 48 | 49 | ### Example 50 | 51 | ```bash 52 | # AMP2-C1 available at fr-par-2 for testing, as cheap as STARDUST1-S, but arm64 instead of x86_64 53 | 54 | vm_name=test zone=fr-par-2 type=AMP2-C1 ./create-scw-wireguard_pi-hole_unbound.sh 55 | 56 | ``` 57 | 58 | Note the parameters `vm_name`, `zone` and `type` in the command-line. 59 | Default values will be `wireguard-vps`, `nl-ams-1` and `DEV1-S` otherwise. 60 | 61 | __NB__: `[ctrl]+[q]` to close the VM console attached to your terminal. 62 | 63 | ## What it does 64 | 65 | The script [create-scw-wireguard_pi-hole_unbound.sh](./create-scw-wireguard_pi-hole_unbound.sh) will: 66 | 67 | - check the availability for this VM type 68 | - create a VM 69 | - attach the console to the running terminal 70 | - run the [cloud-init script](./cloud-init/wireguard_pi-hole_unbound.sh). 71 | 72 | The script [basic_script.sh](./basic_script.sh) does exactly the same, but without any check or information display. 73 | 74 | ## The cloud-init part 75 | 76 | The [cloud-init script](./cloud-init/wireguard_pi-hole_unbound.sh) pushed when creating the instance will: 77 | 78 | - upgrade the OS 79 | - install docker and other things (fail2ban, ...) 80 | - generate a random password for root 81 | - create a config file for Unbound 82 | - configure and harden fail2ban using [fail2ban-endlessh](https://github.com/itskenny0/fail2ban-endlessh) configuration 83 | - clone [endlessh](https://github.com/skeeto/endlessh) in order to build the container image (cf. [docker-compose.yml](./docker-compose.yml) and `endlessh`'s Dockerfile) 84 | - create and start an application stack composed of Unbound, Wireguard, Pi-Hole and Watchtower using docker-compose 85 | - add several blocklists and will also whitelist several domains in Pi-Hole 86 | - set a service to print the login and wireguard client information on the server console 87 | - reboot the OS. 88 | 89 | ## The docker-compose stack 90 | 91 | Very largely inspired/copied from [IAmStoxe/wirehole](https://github.com/IAmStoxe/wirehole), but modified and a bit simplified according to my needs. 92 | 93 | The [docker-compose stack](./docker-compose.yml) relies on: 94 | 95 | - [alpinelinux/unbound](https://hub.docker.com/r/alpinelinux/unbound) (was previously [mvance/unbound](https://github.com/MatthewVance/unbound-docker), but the latter was x86_64-only) 96 | - [linuxserver/docker-wireguard](https://github.com/linuxserver/docker-wireguard) 97 | - [pihole/pihole](https://github.com/pi-hole/pi-hole) 98 | - [containrrr/watchtower](https://github.com/containrrr/watchtower) 99 | - a built on-the-fly [endlessh](https://github.com/skeeto/endlessh) container to harden a bit fail2ban 100 | 101 | Thanks to them for building these docker images, and of course to people involved in these projects. 102 | 103 | 104 | 105 | --- 106 | 107 | ## Scaleway CLI commands examples 108 | 109 | ### How to list available VM types and hourly prices by zone 110 | 111 | ```bash 112 | 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 113 | echo -e "\n== $zone ==\n" 114 | scw instance server-type list --output=human zone=$zone 115 | done 116 | ``` 117 | 118 | ### How to connect to the VM 119 | 120 | Open the console on your VM using the [Scaleway console](https://console.scaleway.com/) and restart the VM if you need to retrieve the root password and/or the wireguard information. 121 | 122 | Alternative: 123 | ```bash 124 | # List instances 125 | scw instance server list zone=all 126 | 127 | # Populate these variables 128 | ZONE= 129 | ID= 130 | 131 | # Reboot instance 132 | scw instance server reboot zone=$ZONE $ID 133 | 134 | # Attach to the instance console ([CTRL]+[Q] to detach from console) 135 | scw instance server console zone=$ZONE $ID 136 | ``` 137 | 138 | ### How to delete a running VM 139 | 140 | ```bash 141 | # List instances 142 | scw instance server list zone=all 143 | 144 | # Populate these variables 145 | ZONE= 146 | ID= 147 | 148 | # Delete instance 149 | scw instance server terminate with-ip=true with-block=true zone=$ZONE $ID 150 | ``` 151 | 152 | ### How to get all available boot images for VMs 153 | 154 | ```bash 155 | scw marketplace image list 156 | ``` 157 | 158 | ## Wireguard client configuration modification 159 | 160 | Objective: route only Internet traffic to the VPN, but keep local network and DNS reachable. 161 | 162 | Use [WireGuard AllowedIPs Calculator](https://www.procustodibus.com/blog/2021/03/wireguard-allowedips-calculator/) to calculate the `AllowedIPs`parameter. 163 | 164 | _Example_: 165 | 166 | ```text 167 | [Interface] 168 | PrivateKey = [REDACTED] 169 | ListenPort = [REDACTED] 170 | Address = 10.6.0.2/32 171 | DNS = [REDACTED - LOCAL DNS IP] 172 | 173 | [Peer] 174 | PublicKey = [REDACTED] 175 | 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 176 | Endpoint = [REDACTED]:[REDACTED] 177 | ``` 178 | 179 | This `AllowedIPs` excludes all local networks according to [RFC1918](https://en.wikipedia.org/w/index.php?title=RFC1918). 180 | 181 | ## Demo 182 | 183 | ![demo](./assets/demo.gif) 184 | (speed is x3) -------------------------------------------------------------------------------- /TEST.md: -------------------------------------------------------------------------------- 1 | # How to test the VPN + DNS resolution 2 | 3 | 4 | ## VPN 5 | 6 | Once the **client**, Wireguard client configured and **started**: 7 | 8 | ```bash 9 | curl -sL http://ifconfig.co 10 | ``` 11 | Should return the VPS IP 12 | 13 | Alternative: 14 | Connect to [http://ifconfig.co](http://ifconfig.co) 15 | 16 | 17 | ## DNS 18 | 19 | 20 | ### On the **VPS**, check default name resolution 21 | 22 | ```bash 23 | dig +short addme.com 24 | ``` 25 | 26 | Should answer an IP address 27 | 28 | 29 | ### On the **VPS**, name resolution explicitely using Unbound 30 | 31 | ```bash 32 | dig +short addme.com @10.2.0.200 33 | ``` 34 | 35 | Should answer the same IP address 36 | 37 | 38 | ### On the **VPS**, name resolution explicitely using Pi-Hole 39 | 40 | ```bash 41 | dig +short addme.com @10.2.0.100 42 | ``` 43 | 44 | Should answer `0.0.0.0` (hence filtered) 45 | 46 | 47 | ### On the **client**, **VPN started**, check name resolution 48 | 49 | ```bash 50 | nslookup addme.com 51 | ``` 52 | 53 | Alterative: 54 | try to reach [http://addme.com](http://addme.com) or [d3ward/toolz](https://d3ward.github.io/toolz/adblock.html). 55 | -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eric-glb/wireguard-vps/8ab5465393d5f6b893ccf25fa6f7d15a905bdc93/assets/demo.gif -------------------------------------------------------------------------------- /assets/demo.tape: -------------------------------------------------------------------------------- 1 | # Where should we write the GIF? 2 | Output assets/demo.gif 3 | 4 | Set PlaybackSpeed 3.0 5 | Set CursorBlink false 6 | 7 | Set WindowBar Colorful 8 | Set FontSize 12 9 | Set Padding 0 10 | Set Width 1280 11 | Set Height 1200 12 | Set TypingSpeed 100ms 13 | 14 | Sleep 3s 15 | Type "./create-scw-wireguard_pi-hole_unbound.sh" 16 | Enter 17 | Sleep 160 18 | Ctrl+Q 19 | Sleep 10 20 | Type "scw instance server terminate with-ip=true with-block=true zone=nl-ams-1 $(scw instance server list zone=all name=wireguard-vps -o json | jq -r '.[].id')" 21 | Enter 22 | Sleep 10 23 | -------------------------------------------------------------------------------- /assets/scw-wireguard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eric-glb/wireguard-vps/8ab5465393d5f6b893ccf25fa6f7d15a905bdc93/assets/scw-wireguard.png -------------------------------------------------------------------------------- /basic_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPTPATH=$( dirname $(realpath "$0") ) 4 | vm_name=${vm_name-wireguard-vps} 5 | zone=${zone-nl-ams-1} 6 | type=${type-DEV1-S} 7 | image=debian_bookworm 8 | script="${SCRIPTPATH}/cloud-init/wireguard_pi-hole_unbound.sh" 9 | 10 | # prerequisites 11 | for bin in scw jq; do 12 | if ! type -P $bin &>/dev/null; then 13 | echo -e "\nERROR: Prerequisite [${bin}] not found. Abort.\n"; exit 1 14 | fi 15 | done 16 | 17 | # create instance 18 | vm_id=$( scw instance server create --output=json ip=new \ 19 | type=${type} zone=${zone} image=${image} \ 20 | name=${vm_name} cloud-init=@${script} \ 21 | | jq -r '.id' ) 22 | 23 | # Success? 24 | [ -n "$vm_id" ] || exit 1 25 | 26 | # attach console 27 | scw instance server console ${vm_id} zone=${zone} 28 | 29 | # once console is detached 30 | echo -e "\nCLI to delete VM $vm_name:\n" 31 | echo -e "scw instance server terminate with-ip=true with-block=true zone=$zone $vm_id\n" 32 | -------------------------------------------------------------------------------- /cloud-init/wireguard_pi-hole_unbound.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Cloud-Init script to run Wireguard+Unbound+PiHole on a fresh virtual server. 4 | # Use case: run this on a fresh cheap Scaleway stardust instance. 5 | 6 | cat <<'EOF' > /dev/console 7 | 8 | ██╗ ██╗██╗██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗ ██████╗ 9 | ██║ ██║██║██╔══██╗██╔════╝██╔════╝ ██║ ██║██╔══██╗██╔══██╗██╔══██╗ 10 | ██║ █╗ ██║██║██████╔╝█████╗ ██║ ███╗██║ ██║███████║██████╔╝██║ ██║ 11 | ██║███╗██║██║██╔══██╗██╔══╝ ██║ ██║██║ ██║██╔══██║██╔══██╗██║ ██║ 12 | ╚███╔███╔╝██║██║ ██║███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ██║██████╔╝ 13 | ╚══╝╚══╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ 14 | ██╗ ██╗██████╗ ███████╗ 15 | ██║ ██║██╔══██╗██╔════╝ 16 | ██║ ██║██████╔╝███████╗ 17 | ╚██╗ ██╔╝██╔═══╝ ╚════██║ 18 | ╚████╔╝ ██║ ███████║ 19 | ╚═══╝ ╚═╝ ╚══════╝ 20 | 21 | EOF 22 | 23 | MY_USER=user 24 | export DEBIAN_FRONTEND=noninteractive 25 | apt-get update 26 | apt-get upgrade -y 27 | apt-get install -y --no-install-recommends \ 28 | python3 pwgen qrencode fail2ban \ 29 | dnsutils jq wireguard docker.io \ 30 | docker-compose git rsyslog 31 | 32 | cat <<'EOF' > /dev/console 33 | 34 | ███████╗██╗ ██╗███████╗████████╗███████╗███╗ ███╗ 35 | ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔════╝████╗ ████║ 36 | ███████╗ ╚████╔╝ ███████╗ ██║ █████╗ ██╔████╔██║ 37 | ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══╝ ██║╚██╔╝██║ 38 | ███████║ ██║ ███████║ ██║ ███████╗██║ ╚═╝ ██║ 39 | ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ 40 | ██████╗ ██████╗ ███╗ ██╗███████╗██╗ ██████╗ 41 | ██╔════╝██╔═══██╗████╗ ██║██╔════╝██║██╔════╝ 42 | ██║ ██║ ██║██╔██╗ ██║█████╗ ██║██║ ███╗ 43 | ██║ ██║ ██║██║╚██╗██║██╔══╝ ██║██║ ██║ 44 | ╚██████╗╚██████╔╝██║ ╚████║██║ ██║╚██████╔╝ 45 | ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ 46 | 47 | EOF 48 | 49 | # Root random password, needed for console access 50 | ROOT_PWD=$(pwgen 24 -1) 51 | echo "root:${ROOT_PWD}" | chpasswd 52 | 53 | # Where we store config files 54 | mkdir -p /docker/{wireguard,unbound,etc-pihole,etc-dnsmasq.d} 55 | 56 | # Unbound config file, butchered 57 | 58 | cat <<'EOF' > /docker/unbound/unbound.conf 59 | server: 60 | cache-max-ttl: 86400 61 | cache-min-ttl: 60 62 | edns-buffer-size: 1472 63 | interface: 0.0.0.0@53 64 | rrset-roundrobin: yes 65 | log-local-actions: no 66 | log-queries: no 67 | log-replies: no 68 | log-servfail: no 69 | verbosity: 1 70 | aggressive-nsec: yes 71 | delay-close: 10000 72 | do-daemonize: no 73 | do-not-query-localhost: no 74 | neg-cache-size: 4M 75 | qname-minimisation: yes 76 | access-control: 127.0.0.1/32 allow 77 | access-control: 192.168.0.0/16 allow 78 | access-control: 172.16.0.0/12 allow 79 | access-control: 10.0.0.0/8 allow 80 | harden-algo-downgrade: yes 81 | harden-below-nxdomain: yes 82 | harden-dnssec-stripped: yes 83 | harden-glue: yes 84 | harden-large-queries: yes 85 | harden-referral-path: no 86 | harden-short-bufsize: yes 87 | hide-identity: yes 88 | hide-version: yes 89 | identity: "DNS" 90 | private-address: 10.0.0.0/8 91 | private-address: 172.16.0.0/12 92 | private-address: 192.168.0.0/16 93 | private-address: 169.254.0.0/16 94 | private-address: fd00::/8 95 | private-address: fe80::/10 96 | private-address: ::ffff:0:0/96 97 | unwanted-reply-threshold: 10000000 98 | val-clean-additional: yes 99 | msg-cache-size: 260991658 100 | num-queries-per-thread: 4096 101 | outgoing-range: 8192 102 | rrset-cache-size: 260991658 103 | minimal-responses: yes 104 | prefetch: yes 105 | prefetch-key: yes 106 | serve-expired: yes 107 | so-reuseport: yes 108 | so-rcvbuf: 1m 109 | remote-control: 110 | control-enable: no 111 | EOF 112 | touch /docker/unbound/unbound.log 113 | 114 | # endlessh: https://github.com/skeeto/endlessh - the image will be build using docker-compose 115 | mkdir -p /docker/endlessh 116 | git clone https://github.com/skeeto/endlessh.git /docker/endlessh 117 | 118 | # https://github.com/itskenny0/fail2ban-endlessh 119 | mkdir /tmp/fail2ban-endlessh 120 | git clone https://github.com/itskenny0/fail2ban-endlessh.git /tmp/fail2ban-endlessh 121 | cp /tmp/fail2ban-endlessh/action.d/endlessh.conf /etc/fail2ban/action.d/endlessh.conf 122 | cp /tmp/fail2ban-endlessh/jail.d/endlessh.conf /etc/fail2ban/jail.d/endlessh.conf 123 | rm -rf /tmp/fail2ban-endlessh 124 | 125 | # Harden fail2ban 126 | sed -i 's//iptables/g' /etc/fail2ban/action.d/endlessh.conf #FIXME? 127 | cat <> /etc/fail2ban/jail.d/endlessh.conf 128 | bantime = 24h 129 | findtime = 12h 130 | maxretry = 3 131 | EOF 132 | 133 | # docker-compose file, Cf. https://github.com/IAmStoxe/wirehole 134 | 135 | cat < /docker/docker-compose.yml 136 | --- 137 | version: "3" 138 | 139 | networks: 140 | private_network: 141 | ipam: 142 | driver: default 143 | config: 144 | - subnet: 10.2.0.0/24 145 | 146 | services: 147 | unbound: 148 | image: "alpinelinux/unbound:latest" 149 | container_name: unbound 150 | restart: unless-stopped 151 | hostname: "unbound" 152 | volumes: 153 | - "./unbound:/etc/unbound" 154 | networks: 155 | private_network: 156 | ipv4_address: 10.2.0.200 157 | 158 | wireguard: 159 | depends_on: [unbound, pihole] 160 | image: ghcr.io/linuxserver/wireguard 161 | container_name: wireguard 162 | cap_add: 163 | - NET_ADMIN 164 | - SYS_MODULE 165 | environment: 166 | - PUID=1000 167 | - PGID=1000 168 | - TZ=Europe/Paris 169 | - SERVERPORT=51820 170 | - PEERS=${MY_USER} 171 | - PEERDNS=10.2.0.100 172 | - INTERNAL_SUBNET=10.6.0.0 173 | volumes: 174 | - ./wireguard:/config 175 | - /lib/modules:/lib/modules 176 | ports: 177 | - 51820:51820/udp 178 | dns: 179 | - 10.2.0.100 # Points to pihole 180 | - 10.2.0.200 # Points to unbound 181 | sysctls: 182 | - net.ipv4.conf.all.src_valid_mark=1 183 | restart: unless-stopped 184 | networks: 185 | private_network: 186 | ipv4_address: 10.2.0.3 187 | 188 | pihole: 189 | depends_on: [unbound] 190 | container_name: pihole 191 | image: pihole/pihole:latest 192 | restart: unless-stopped 193 | hostname: pihole 194 | dns: 195 | - 127.0.0.1 196 | - 10.2.0.200 # Points to unbound 197 | environment: 198 | TZ: "Europe/Paris" 199 | WEBPASSWORD: "" # Blank password 200 | ServerIP: 10.1.0.100 # Internal IP of pihole 201 | DNS1: 10.2.0.200 # Unbound IP 202 | DNS2: 10.2.0.200 # If we don't specify two, it will auto pick google. 203 | volumes: 204 | - "./etc-pihole/:/etc/pihole/" 205 | - "./etc-dnsmasq.d/:/etc/dnsmasq.d/" 206 | # Recommended but not required (DHCP needs NET_ADMIN) 207 | # https://github.com/pi-hole/docker-pi-hole#note-on-capabilities 208 | #cap_add: 209 | # - NET_ADMIN 210 | networks: 211 | private_network: 212 | ipv4_address: 10.2.0.100 213 | 214 | watchtower: 215 | container_name: watchtower 216 | image: containrrr/watchtower 217 | restart: unless-stopped 218 | hostname: watchtower 219 | volumes: 220 | - /var/run/docker.sock:/var/run/docker.sock 221 | environment: 222 | WATCHTOWER_POLL_INTERVAL: 86400 223 | WATCHTOWER_CLEANUP: "true" 224 | 225 | endlessh: 226 | image: endlessh:latest 227 | build: ./endlessh 228 | container_name: endlessh 229 | hostname: endlessh 230 | restart: unless-stopped 231 | ports: 232 | - 2222:2222 233 | EOF 234 | 235 | cd /docker || return 236 | 237 | cat <<'EOF' > /dev/console 238 | 239 | ██████╗ ██╗ ██╗██╗██╗ ██████╗ 240 | ██╔══██╗██║ ██║██║██║ ██╔══██╗ 241 | ██████╔╝██║ ██║██║██║ ██║ ██║ 242 | ██╔══██╗██║ ██║██║██║ ██║ ██║ 243 | ██████╔╝╚██████╔╝██║███████╗██████╔╝ 244 | ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ 245 | ██╗███╗ ███╗ █████╗ ██████╗ ███████╗███████╗ 246 | ██║████╗ ████║██╔══██╗██╔════╝ ██╔════╝██╔════╝ 247 | ██║██╔████╔██║███████║██║ ███╗█████╗ ███████╗ 248 | ██║██║╚██╔╝██║██╔══██║██║ ██║██╔══╝ ╚════██║ 249 | ██║██║ ╚═╝ ██║██║ ██║╚██████╔╝███████╗███████║ 250 | ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ 251 | 252 | EOF 253 | docker-compose build endlessh 2>/dev/null 254 | 255 | cat <<'EOF' > /dev/console 256 | 257 | ██████╗ ██╗ ██╗██╗ ██╗ 258 | ██╔══██╗██║ ██║██║ ██║ 259 | ██████╔╝██║ ██║██║ ██║ 260 | ██╔═══╝ ██║ ██║██║ ██║ 261 | ██║ ╚██████╔╝███████╗███████╗ 262 | ╚═╝ ╚═════╝ ╚══════╝╚══════╝ 263 | 264 | ██╗███╗ ███╗ █████╗ ██████╗ ███████╗███████╗ 265 | ██║████╗ ████║██╔══██╗██╔════╝ ██╔════╝██╔════╝ 266 | ██║██╔████╔██║███████║██║ ███╗█████╗ ███████╗ 267 | ██║██║╚██╔╝██║██╔══██║██║ ██║██╔══╝ ╚════██║ 268 | ██║██║ ╚═╝ ██║██║ ██║╚██████╔╝███████╗███████║ 269 | ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ 270 | 271 | EOF 272 | docker-compose pull 273 | 274 | cat <<'EOF' > /dev/console 275 | 276 | ███████╗████████╗ █████╗ ██████╗ ████████╗ 277 | ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝ 278 | ███████╗ ██║ ███████║██████╔╝ ██║ 279 | ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ 280 | ███████║ ██║ ██║ ██║██║ ██║ ██║ 281 | ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ 282 | 283 | ███████╗████████╗ █████╗ ██████╗██╗ ██╗ 284 | ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ 285 | ███████╗ ██║ ███████║██║ █████╔╝ 286 | ╚════██║ ██║ ██╔══██║██║ ██╔═██╗ 287 | ███████║ ██║ ██║ ██║╚██████╗██║ ██╗ 288 | ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ 289 | 290 | EOF 291 | docker-compose up -d 292 | 293 | # Wait for wireguard image container to create config file 294 | CONF="/docker/wireguard/peer_${MY_USER}/peer_${MY_USER}.conf" 295 | while : 296 | do 297 | [ -e ${CONF} ] && break 298 | sleep 2 299 | done 300 | 301 | # Add some blocklists and entries in the whiltelist in Pi-Hole 302 | docker exec pihole sqlite3 /etc/pihole/gravity.db "INSERT INTO adlist (address, enabled, comment) VALUES ('https://www.github.developerdan.com/hosts/lists/ads-and-tracking-extended.txt', 1, 'Developer Dan - Ads & Tracking');" 303 | docker exec pihole sqlite3 /etc/pihole/gravity.db "INSERT INTO adlist (address, enabled, comment) VALUES ('https://www.github.developerdan.com/hosts/lists/dating-services-extended.txt', 1, 'Developer Dan - Dating Services');" 304 | for domain in spclient.wg.spotify.com fonts.gstatic.com; do 305 | docker exec pihole pihole -w $domain 306 | done 307 | 308 | # Store credentials information in /root/banner file 309 | SEPARATOR=$(perl -le 'print "─" x 80') 310 | ( 311 | echo -e "${SEPARATOR}\nWireguard configuration\n${SEPARATOR}\n" 312 | qrencode -t ansiutf8 < ${CONF} | sed 's/^/ /' 313 | echo -e "\n${SEPARATOR}" 314 | cat ${CONF} 315 | echo -e "${SEPARATOR}\nRoot password for console access: ${ROOT_PWD}\n${SEPARATOR}" 316 | echo -e "Connect to this server:\n ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$(curl -sL https://ifconfig.co/)\n${SEPARATOR}" 317 | ) > /root/banner 318 | 319 | # Systemd service to print credentials on the console at boot time 320 | cat <<'EOF' > /lib/systemd/system/banner-console.service 321 | [Unit] 322 | Description=Show information on the console 323 | After=cloud-init.target 324 | 325 | [Service] 326 | Type=oneshot 327 | ExecStart=/usr/bin/bash -c "/usr/bin/cat /root/banner > /dev/console" 328 | [Install] 329 | WantedBy=cloud-init.target 330 | EOF 331 | 332 | systemctl daemon-reload 333 | systemctl enable banner-console 334 | 335 | #restart VM 336 | cat <<'EOF' > /dev/console 337 | 338 | ██████╗ ███████╗██████╗ ██████╗ ██████╗ ████████╗ 339 | ██╔══██╗██╔════╝██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ 340 | ██████╔╝█████╗ ██████╔╝██║ ██║██║ ██║ ██║ 341 | ██╔══██╗██╔══╝ ██╔══██╗██║ ██║██║ ██║ ██║ 342 | ██║ ██║███████╗██████╔╝╚██████╔╝╚██████╔╝ ██║ 343 | ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ 344 | 345 | ██████╗ ███████╗███╗ ██╗██████╗ ██╗███╗ ██╗ ██████╗ 346 | ██╔══██╗██╔════╝████╗ ██║██╔══██╗██║████╗ ██║██╔════╝ 347 | ██████╔╝█████╗ ██╔██╗ ██║██║ ██║██║██╔██╗ ██║██║ ███╗ 348 | ██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║██║██║╚██╗██║██║ ██║ 349 | ██║ ███████╗██║ ╚████║██████╔╝██║██║ ╚████║╚██████╔╝ 350 | ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ 351 | 352 | EOF 353 | reboot -------------------------------------------------------------------------------- /create-scw-wireguard_pi-hole_unbound.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Object: create a cheap VPS VM as VPN Wireguard server, with Unbound and Pi-Hole. 4 | # 5 | # Example: 6 | # vm_name=test zone=fr-par-2 type=AMP2-C1 ./create-scw-wireguard_pi-hole_unbound.sh 7 | # 8 | 9 | #-> Target <-------------------------------------------------------------------- 10 | 11 | SCHEMA=$(cat <<'EOF' 12 | 13 | .-~~~-. ┌───────────────────────────────────┐ 14 | .- ~ ~-( )_ _ │VPS │ 15 | / Internet ~ -. │ ┌────────────┐ DNS │ 16 | | ◄──────┼─┤Unbound ◄───────┐ │ 17 | \ ▲ .' │ │(DNS solver)│ │ │ 18 | ~- ._ ,..,.,.,., ,.│ -~ │ └────────────┘ │ │ 19 | ' │ │ ┌──────┴─────┐ │ 20 | ┌─────────────────┐ │ │ │Pi-Hole │ │ 21 | │ PC/Phone │ │ │ │(DNS filter)│ │ 22 | │ │ │ │ └──────▲─────┘ │ 23 | │ ┌─────────┐ │ │ │ ┌─────────┐ │ │ 24 | │ │Wireguard│ │ └────────┼──┤Wireguard├─────────┘ │ 25 | │ │ Client │ │ │ │ Server │ DNS │ 26 | │ │ │ ─┴──────────────────┴─ │ (VPN) │ │ 27 | │ │ ├──► VPN Tunnel ──► │ ┌────────────────┐ │ 28 | │ └─────────┘ ─┬──────────────────┬─ └─────────┘ │Watchtower │ │ 29 | │ │ │ │(images updater)│ │ 30 | └─────────────────┘ │ └────────────────┘ │ 31 | └───────────────────────────────────┘ 32 | EOF 33 | ) 34 | 35 | #-> Variables <----------------------------------------------------------------- 36 | 37 | SCRIPTPATH=$( dirname $(realpath "$0") ) 38 | vm_name=${vm_name-wireguard-vps} # Default: wireguard-vps 39 | zone=${zone-nl-ams-1} # Default: nl-ams-1 40 | type=${type-DEV1-S} # Default: DEV1-S, as STARDUST1-S mainly unavailable 41 | image=debian_bookworm # OS 42 | script="${SCRIPTPATH}/cloud-init/wireguard_pi-hole_unbound.sh" # cloud-init script 43 | 44 | #-> Check prerequisites <------------------------------------------------------- 45 | 46 | for bin in scw jq perl tput; do 47 | if ! type -P $bin &>/dev/null; then 48 | echo -e "\nPrerequisite '$bin' not found. Abort.\n" 49 | exit 1 50 | fi 51 | done 52 | 53 | #-> Colors, etc. <-------------------------------------------------------------- 54 | 55 | R="\e[0;31m"; Y="\e[0;33m"; G="\e[0;32m"; C="\e[0;m" 56 | sep(){ perl -le 'print "─" x $ARGV[0]' "${1-$(tput cols)}"; } 57 | 58 | #-> Prerequisite: `scw init` done <--------------------------------------------- 59 | 60 | projId=$(scw info --output=json | 61 | jq -r '.settings[] 62 | | select ( .key == "default_project_id" ) 63 | | .value' 64 | ) 65 | if [ -z "$projId" ]; then 66 | echo -e "\n${R}Error${C}: please run '${G}scw init${C}' before anything else. Abort.\n" 67 | exit 1 68 | fi 69 | 70 | #-> Help <---------------------------------------------------------------------- 71 | 72 | for param in $@; do 73 | if grep -qE '^(\-h|\-\-help)$' <<<"$param"; then 74 | echo -e "\n${R}Object${C}: ${G}create a cheap VPS VM as VPN Wireguard server, with Unbound and Pi-Hole.${C}" 75 | echo -e "$SCHEMA\n" 76 | echo -e "${Y}Usage example${C}:\n\n${G}vm_name=test zone=fr-par-2 type=AMP2-C1 $0${C}\n" 77 | exit 0 78 | else 79 | echo -e "\n${R}Error${C}: incorrect parameter ${Y}${param}${C}. Abort.\n" 80 | exit 1 81 | fi 82 | done 83 | 84 | #-> Check VM availability <----------------------------------------------------- 85 | 86 | TYPE_LIST=$(scw instance server-type list --output=json zone=$zone) 87 | AVAIL=$(jq -r --arg TYPE "$type" '.[] | select(.name == $TYPE) | .availability' <<<$TYPE_LIST) 88 | if [ "$AVAIL" != "available" ]; then 89 | echo -e "\n${R}Error${C}: VM type ${Y}${type}${C} is not available in zone ${Y}${zone}${C}.\n" && exit 1 90 | fi 91 | 92 | #-> Info <---------------------------------------------------------------------- 93 | 94 | clear; sep 95 | echo -e "\nCreating Scaleway VM:\n" 96 | echo -e " - name: ${Y}${vm_name}${C}" 97 | echo -e " - type: ${Y}${type}${C}" 98 | echo -e " - zone: ${Y}${zone}${C}" 99 | echo -e " - script: ${Y}${script}${C}\n" 100 | sep 101 | echo -e "The console will be attached to this terminal.\n${R}[CTRL]${C}+${R}[Q]${C} to close it ${G}once finished${C}.\n" 102 | 103 | #-> tput "magic" to print screen footer while waiting for the console <--------- 104 | 105 | content=(); IFS=$'\n'$'\r'; while read -r line; do content+=("$line"); done <<<"$SCHEMA"; IFS= 106 | vLen=${#content[@]}; hLen=0; for i in "${content[@]}"; do [ ${#i} -gt $hLen ] && hLen=${#i}; done 107 | totalvLen=$(tput lines); totalhLen=$(tput cols); lp=$(((totalhLen - hLen) / 2)) 108 | tput sc 109 | tput cup $(( totalvLen - vLen - 4 )) 0 110 | for i in "${content[@]}"; do seq 1 $lp | xargs printf " %.0s"; echo -e "${G}$i${C}"; done 111 | tput rc 112 | 113 | #-> Create VM <----------------------------------------------------------------- 114 | vm_id=$( scw instance server create --output=json ip=new \ 115 | type=${type} zone=${zone} image=${image} \ 116 | name=${vm_name} cloud-init=@${script} 2>/dev/null \ 117 | | jq -r '.id' 118 | ) 119 | 120 | if [ -n "$vm_id" ]; then 121 | echo -e "instance ${Y}${vm_name}${C} (${Y}${vm_id}${C}) created successfully${C}\n" 122 | else 123 | echo -e "\n${R}Error${C}: instance ${Y}${vm_name}${C} has not been created successfully\n" 124 | exit 1 125 | fi 126 | 127 | #-> Attach console <------------------------------------------------------------ 128 | 129 | scw instance server console ${vm_id} zone=${zone} 130 | 131 | #-> post-install info, once the console is detached <--------------------------- 132 | 133 | IP=$(scw instance server get zone=$zone $vm_id --output=json | jq -r '.public_ip.address') 134 | echo -en "${R}";sep;echo -en "${C}" 135 | scw instance server list zone=all 136 | echo -en "${R}";sep;echo -en "${C}" 137 | echo -e "\nHow to connect to VM ${Y}${vm_name}${C}:\n" 138 | echo -e "${G}ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@${Y}${IP}\n" 139 | echo -e "${C}\nHow to delete VM ${Y}${vm_name}${C}:\n" 140 | echo -e "${G}scw instance server terminate with-ip=true with-block=true zone=${Y}${zone}${G} ${Y}${vm_id}${C}\n" 141 | echo -en "${R}";sep;echo -e "${C}" 142 | --------------------------------------------------------------------------------