Initial IPv6 delployment #2

Merged
CaZzzer merged 2 commits from feature/ipv6 into master 2023-04-04 08:56:13 +00:00
24 changed files with 254 additions and 56 deletions

1
.idea/alpina.iml generated
View File

@ -28,6 +28,7 @@
<option value="$MODULE_DIR$/roles/nextcloud/templates" />
<option value="$MODULE_DIR$/roles/arrstack/templates" />
<option value="$MODULE_DIR$/roles/jellyfin/templates" />
<option value="$MODULE_DIR$/roles/docker_host/templates" />
</list>
</option>
</component>

23
.idea/jsonSchemas.xml generated
View File

@ -3,6 +3,24 @@
<component name="JsonSchemaMappingsProjectConfiguration">
<state>
<map>
<entry key="Ansible Tasks File">
<value>
<SchemaInfo>
<option name="name" value="Ansible Tasks File" />
<option name="relativePathToSchema" value="https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/ansible.json#/$defs/tasks" />
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/tasks/*.yml" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="Traefik v2">
<value>
<SchemaInfo>
@ -45,6 +63,11 @@
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/docker-compose.yml" />
<option name="mappingKind" value="Pattern" />
</Item>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/docker-compose.yml.j2" />

View File

@ -1,4 +1,6 @@
.POSIX:
.PHONY: *
.EXPORT_ALL_VARIABLES:
env ?= staging
vault_id ?= alpina@contrib/rbw-client.sh

View File

@ -2,3 +2,17 @@
A home for configuring all of my homelab containers on a Debian Linux machine.
This assumes a Debian Linux machine with Docker and Docker Compose installed.
# Notes
## IPv6
The current configuration is designed to work with IPv6.
However, because of how (not properly) I'm doing the subnetting
from the host's network, NDP doesn't work.
This means that container IPs are not accessible from other hosts on the local network.
I simply have a static route on my router to the container subnet,
that uses the IP of this host as the gateway.
This is a limitation of my current ISP, I only have a single /64 subnet for my lab network.
I'd like to get a /56 or /48, perhaps using Hurricane Electric's tunnel broker.
*Sigh* ISPs being stingy with the 2^48 prefixes they're afraid of running out of.

View File

@ -1,3 +1,11 @@
{% macro default_network(subnet_index) %}
default:
enable_ipv6: true
ipam:
config:
- subnet: {{ docker_ipv6_subnet | ansible.utils.ipsubnet(80, subnet_index) }}
{% endmacro %}
{% macro traefik_labels(host, service="", port="", auth=false) %}
traefik.enable=true
- traefik.http.routers.{{ host }}.rule=Host(`{{ host }}.{{ domain }}`)

View File

@ -28,8 +28,12 @@
when: item.state == "file"
- name: Deploy docker-compose for {{ current_svc_name }}
community.docker.docker_compose:
project_src: "{{ current_svc_path }}"
state: present
pull: true
remove_orphans: true
command: docker compose -f "{{ current_svc_path }}/docker-compose.yml" up -d --pull --remove-orphans
register: docker_compose_output
# Not perfect idempotency, but the built-in docker_compose module doesn't support docker-compose v2
# And of course there's an IPv6 bug in docker-compose v1, smh
# https://github.com/docker/compose/issues/7670
changed_when: "'created' in docker_compose_output.stderr.lower()"
- debug:
var: docker_compose_output

View File

@ -0,0 +1 @@
docker_ipv6_index: 255

View File

@ -0,0 +1 @@
docker_ipv6_index: 254

14
poetry.lock generated
View File

@ -254,6 +254,18 @@ files = [
{file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
]
[[package]]
name = "netaddr"
version = "0.8.0"
description = "A network address manipulation library for Python"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"},
{file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"},
]
[[package]]
name = "packaging"
version = "23.0"
@ -366,4 +378,4 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "4c3656f66006d184debf3777b8df073898df0eb1f53611cdd47ec4c543071595"
content-hash = "e5cad99dc808b0751037fa8d524f2a4c4eac8f6ae5710c6ed5f32def518746b9"

View File

@ -9,6 +9,7 @@ readme = "README.md"
python = "^3.10"
ansible = "^7.3.0"
ansible-vault = "^2.1.0"
netaddr = "^0.8.0"
[build-system]

View File

@ -1,6 +1,5 @@
#!/usr/bin/env bash
apk add --no-cache wireguard-tools-wg
set -x
local_gateway=$(ip route | grep default | awk '{print $3}')
# This used as the gateway address for NAT-PMP to work properly
@ -19,5 +18,9 @@ ip route add default via "$wg_gateway"
# Note that the DNS isn't changed, so there's actually a leak there
# That's on purpose, just in case I want to access local jackett from qbit
# Still need to figure out how to make this work with IPv6
# Prevent IPv6 leaks
# ip -6 route del default
# Finally, optionally allow access to the home network
# ip route add "\{\{ home_network }}" via "$local_gateway"

View File

@ -1,9 +1,11 @@
{% from "contrib/compose_helpers.j2" import traefik_labels with context %}
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.7"
version: "3.9"
networks:
default:
{# {{ helpers.default_network(249) | indent(2) }}#}
# TODO: Figure out IPv6 leaks
ipv4_only:
traefik_traefik:
external: true
@ -14,10 +16,13 @@ services:
cap_add:
- NET_ADMIN
labels:
- {{ traefik_labels("qbit", port="8080", auth=true) | indent(6) }}
- {{ helpers.traefik_labels('qbit', port='8080', auth=true) | indent(6) }}
restart: unless-stopped
environment:
- DOCKER_MODS=linuxserver/mods:universal-package-install
- INSTALL_PACKAGES=wireguard-tools-wg
networks:
- default
- ipv4_only
- traefik_traefik
volumes:
- ./wireguard:/etc/wireguard:ro
@ -31,12 +36,12 @@ services:
image: linuxserver/prowlarr:latest
container_name: prowlarr
labels:
- {{ traefik_labels("prowlarr", port="9696", auth=true) | indent(6) }}
- {{ helpers.traefik_labels('prowlarr', port='9696', auth=true) | indent(6) }}
restart: unless-stopped
depends_on:
- qbittorrent
networks:
- default
- ipv4_only
- traefik_traefik
volumes:
- {{ base_volume_path }}/arrstack/config/prowlarr:/config
@ -45,12 +50,12 @@ services:
image: linuxserver/sonarr:latest
container_name: sonarr
labels:
- {{ traefik_labels("sonarr", port="8989", auth=true) | indent(6) }}
- {{ helpers.traefik_labels('sonarr', port='8989', auth=true) | indent(6) }}
restart: unless-stopped
depends_on:
- qbittorrent
networks:
- default
- ipv4_only
- traefik_traefik
volumes:
- {{ base_volume_path }}/arrstack/config/sonarr:/config
@ -61,12 +66,12 @@ services:
image: linuxserver/radarr:latest
container_name: radarr
labels:
- {{ traefik_labels("radarr", port="7878", auth=true) | indent(6) }}
- {{ helpers.traefik_labels('radarr', port='7878', auth=true) | indent(6) }}
restart: unless-stopped
depends_on:
- qbittorrent
networks:
- default
- ipv4_only
- traefik_traefik
volumes:
- {{ base_volume_path }}/arrstack/config/radarr:/config

View File

@ -7,5 +7,5 @@ PrivateKey = {{ wg_privkey }}
[Peer]
PublicKey = {{ wg_peer_pubkey }}
AllowedIPs = 0.0.0.0/0
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint = {{ wg_peer_endpoint }}

View File

@ -1,9 +1,9 @@
{% from "contrib/compose_helpers.j2" import traefik_labels with context %}
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.7"
version: "3.9"
networks:
default:
{{ helpers.default_network(253) | indent(2) }}
traefik_traefik:
external: true
@ -12,11 +12,14 @@ services:
image: ghcr.io/goauthentik/server:${AUTHENTIK_VERSION}
container_name: authentik_server
labels:
- {{ traefik_labels("auth", port="9000") | indent(6) }}
- traefik.http.middlewares.authentik.forwardauth.address=http://authentik_server:9000/outpost.goauthentik.io/auth/traefik
- {{ helpers.traefik_labels('auth', port='9000') | indent(6) }}
- traefik.http.middlewares.authentik.forwardauth.address=http://localhost:9000/outpost.goauthentik.io/auth/traefik
- traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true
- traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version
restart: unless-stopped
# Port forward is needed because traefik can't resolve the container name from the host network
ports:
- "9000:9000"
command: server
env_file:
- .env.authentik
@ -61,3 +64,5 @@ services:
interval: 30s
retries: 5
timeout: 3s
volumes:
- {{ base_volume_path }}/authentik/redis:/data

View File

@ -1,3 +1,12 @@
- name: Install Debian packages
become: yes
ansible.builtin.apt:
name:
- docker-ce
- docker-compose-plugin
- firewalld
state: latest
- name: Upgrade Debian packages
become: yes
ansible.builtin.apt:
@ -8,6 +17,44 @@
state: latest
register: apt_upgrades
- name: Allow SSH
become: yes
firewalld:
service: ssh
permanent: yes
state: enabled
immediate: yes
- name: Allow Web
become: yes
firewalld:
service: http
permanent: yes
state: disabled
immediate: yes
- name: Allow Web Secure
become: yes
firewalld:
service: https
permanent: yes
state: enabled
immediate: yes
- name: Allow 443 udp for http3
become: yes
firewalld:
port: 443/udp
permanent: yes
state: enabled
immediate: yes
- name: Enable Firewall
become: yes
firewalld:
state: enabled
immediate: yes
- name: Reboot if needed
become: yes
ansible.builtin.reboot:

View File

@ -3,3 +3,41 @@
state: directory
path: "{{ my_svc_path }}"
mode: "700"
- name: Get IPv6 subnet for Docker
set_fact:
docker_ipv6_subnet: "{{ \
ansible_default_ipv6.address \
| ansible.utils.ipsubnet(64) \
| ansible.utils.ipsubnet(72, docker_ipv6_index) \
}}"
- debug:
var: docker_ipv6_subnet
- name: Configure Docker daemon
become: yes
template:
src: "daemon.json.j2"
dest: "/etc/docker/daemon.json"
owner: root
group: root
mode: "0644"
register: docker_daemon_config
- name: Remove docker0 from firewalld trusted zone
become: yes
firewalld:
zone: trusted
interface: docker0
permanent: yes
immediate: yes
state: disabled
register: docker0_firewalld
- name: Restart Docker daemon
become: yes
service:
name: docker
state: restarted
when: docker_daemon_config.changed or docker0_firewalld.changed

View File

@ -0,0 +1,4 @@
{
"ipv6": true,
"fixed-cidr-v6": "{{ docker_ipv6_subnet | ansible.utils.ipsubnet(80, 0) }}"
}

View File

@ -1,9 +1,9 @@
{% from "contrib/compose_helpers.j2" import traefik_labels with context %}
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.7"
version: "3.9"
networks:
default:
{{ helpers.default_network(252) | indent(2) }}
traefik_traefik:
external: true
@ -12,7 +12,7 @@ services:
image: gitea/gitea:1.18
container_name: gitea_server
labels:
- {{ traefik_labels("gitea", port="3000") | indent(6) }}
- {{ helpers.traefik_labels('gitea', port='3000') | indent(6) }}
restart: unless-stopped
env_file:
- .env.gitea

View File

@ -1,9 +1,9 @@
{% from "contrib/compose_helpers.j2" import traefik_labels with context %}
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.7"
version: "3.9"
networks:
default:
{{ helpers.default_network(250) | indent(2) }}
traefik_traefik:
external: true
@ -12,7 +12,7 @@ services:
image: jellyfin/jellyfin:10.8.6
container_name: jellyfin_jellyfin
labels:
- {{ traefik_labels("jellyfin", port="8096") | indent(6) }}
- {{ helpers.traefik_labels('jellyfin', port='8096') | indent(6) }}
restart: unless-stopped
env_file:
- .env.jellyfin

View File

@ -1,9 +1,9 @@
{% from "contrib/compose_helpers.j2" import traefik_labels with context %}
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.7"
version: "3.9"
networks:
default:
{{ helpers.default_network(251) | indent(2) }}
traefik_traefik:
external: true
@ -83,7 +83,7 @@ services:
image: nginx:1.23-alpine
container_name: nextcloud_web
labels:
- {{ traefik_labels("nc") | indent(6) }}
- {{ helpers.traefik_labels('nc') | indent(6) }}
restart: unless-stopped
links:
- app

View File

@ -1,35 +1,37 @@
{% from "contrib/compose_helpers.j2" import traefik_labels with context %}
{% import 'contrib/compose_helpers.j2' as helpers with context %}
{##}
version: "3.7"
version: "3.9"
networks:
default:
traefik:
internal: true
enable_ipv6: true
ipam:
config:
- subnet: {{ traefik_ip }}/24
- subnet: {{ docker_ipv6_subnet | ansible.utils.ipsubnet(80, 255) }}
services:
traefik:
image: traefik:v2.9
container_name: traefik
labels:
- {{ traefik_labels("traefik", service="api@internal") | indent(6) }}
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "8080:8080"
env_file:
- .env.traefik
networks:
default:
traefik:
ipv4_address: {{ traefik_ip }}
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- {{ base_volume_path }}/traefik/rules:/rules:ro
- ./rules:/rules:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- {{ base_volume_path }}/traefik/rules:/rules/extra:ro
- {{ base_volume_path }}/traefik/logs:/logs
- {{ base_volume_path }}/traefik/acme:/acme
# This is mostly just so that the traefik network gets created
whoami:
image: containous/whoami
container_name: whoami
labels:
- {{ helpers.traefik_labels('whoami', port=80) | indent(6) }}
networks:
- traefik

View File

@ -0,0 +1,25 @@
http:
routers:
traefik-dash:
rule: "Host(`traefik.{{ domain }}`)"
entryPoints:
- web
service: traefik-dash
traefik-dash-tls:
rule: "Host(`traefik.{{ domain }}`)"
entryPoints:
- websecure
service: traefik-dash
tls:
certResolver: letsencrypt
domains:
- main: "{{ domain }}"
sans:
- "*.{{ domain }}"
services:
traefik-dash:
loadBalancer:
servers:
- url: "http://localhost:8080"

View File

@ -8,14 +8,16 @@ accessLog:
filePath: /logs/access.log
bufferingSize: 100
experimental:
http3: true
entryPoints:
web:
address: ":80"
forwardedHeaders:
trustedIPs:
- "172.16.0.0/12"
websecure:
address: ":443"
http3:
advertisedPort: 443
certificatesResolvers:
letsencrypt:

View File

@ -3,9 +3,9 @@
- docker_host
- traefik
- authentik
- gitea
- nextcloud
- jellyfin
- gitea
- arrstack
post_tasks:
- name: Docker prune objects