├── .drone.yml
├── .env.example
├── .gitignore
├── README.md
├── docker-compose.yml
├── docs
├── config.boot
├── edgerouter-backups.md
├── images
│ ├── cfapi.png
│ ├── pihole-advanced.png
│ ├── pihole-dns.png
│ ├── router-dhcp.png
│ ├── router-dnsmasq.png
│ └── router-system.png
├── lan-only-routes.md
├── pihole-dnsmasq.md
├── ubuntu-expand-lvm.md
├── wildcard-certs.md
└── wireguard-question.md
├── etc
├── authconfig.ini
├── clickhouse
│ ├── clickhouse-config.xml
│ └── clickhouse-user-config.xml
├── mdadm.conf
├── sshd_config
├── traefik-logrotate.conf
├── traefik
│ └── rules-fail2ban.yml
└── unbound.conf
├── logrotate.service
├── logrotate.timer
├── media-4tb.mount
├── media-primary.mount
├── media-secondary.mount
├── media-wildy.mount
└── wildy.compose.yml
/.drone.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: deploy
5 |
6 | steps:
7 | - name: ssh commands
8 | image: appleboy/drone-ssh
9 | settings:
10 | port: 22
11 | host:
12 | from_secret: ssh_host
13 | username:
14 | from_secret: ssh_user
15 | key:
16 | from_secret: ssh_key
17 | script_stop: true
18 | script:
19 | - source .profile
20 | - cd selfhosted
21 | - git pull
22 | - docker compose config
23 | - docker compose up -d --remove-orphans
24 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | COMPOSE_PROJECT_NAME="stack"
2 |
3 | # Letsencrypt email isn't associated with any account.
4 | # You can put anything here
5 | LETSENCRYPT_EMAIL=letsencrypt@subdavis.com
6 |
7 | # Your naked domain.
8 | DNS_DOMAIN=subdavis.com
9 | DNS_DOMAIN_ZONE_ID=94618f842289a88454b75dba13d9204b
10 |
11 | # The particular DNS record to update with an A record for dyndns
12 | SUBDOMAIN=core
13 |
14 | # Optional pilot token
15 | TRAEFIK_PILOT_TOKEN=CHANGEME
16 |
17 | # Your local subnet, for optional IP Whitelisting in Traefik
18 | # https://tools.ietf.org/html/rfc1918
19 | SUBNET="192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
20 | LOCAL_NETWORK="192.168.52.0/20"
21 |
22 | # Mount locations for external media
23 | PRIMARY_MOUNT=/media/primary
24 | SECONDARY_MOUNT=/media/secondary
25 | MEDIA_MOUNT=/media/4tb
26 | LOCAL_MOUNT=/media/local
27 | TIME_ZONE="America/New_York"
28 |
29 | # Docker socket path
30 | SOCK_PATH=/run/user/1000/docker.sock
31 |
32 | # Everything below here is mostly API keys and passwords.
33 | # Often, more than 1 container needs a given secret.
34 | # I could have put each of these in a separate unit conf directory
35 | # But I was too lazy
36 | AUTH_PROVIDERS_GOOGLE_CLIENT_ID=CHANGEME
37 | AUTH_PROVIDERS_GOOGLE_CLIENT_SECRET=CHANGEME
38 | AUTH_SECRET=CHANGEME
39 | AUTH_WHITELIST="CHANGEME@CHANGEME,ANOTHER@CHANGEME"
40 |
41 | # You'll use these as your AWS ACCESS KEY and AWS SECRET KEY, respectively
42 | MINIO_ACCESS_KEY=CHANGEME
43 | MINIO_SECRET_KEY=CHANGEME
44 |
45 | # For FileRun
46 | FR_DB_USER=filerun
47 | FR_DB_PASS=CHANGEME
48 | FR_DB_ROOT_PW=CHANGEME
49 |
50 | # For DYNDNS and TRAEFIK DNS ACME auth
51 | CF_EMAIL=CHANGEME
52 | CF_TOKEN=CHANGEME
53 |
54 | # PUID/PGID for linuxserver images
55 | XID=1000
56 | # Transition to safer uid
57 | PUID=1001
58 |
59 | # For DRONE
60 | DRONE_GITHUB_CLIENT_ID=CHANGEME
61 | DRONE_GITHUB_CLIENT_SECRET=CHANGEME
62 | DRONE_RPC_SECRET=CHANGEME
63 | # restrict login to me, set me as user
64 | DRONE_USER_FILTER=subdavis
65 | DRONE_USER_CREATE=username:subdavis,admin:true
66 |
67 | # For torrent
68 | OPENVPN_PROVIDER=NORDVPN
69 | TORRENT_USERNAME=CHANGEME
70 | TORRENT_PASSWORD=CHANGEME
71 |
72 | # For plausible
73 | PLAUSIBLE_SECRET_KEY=CHANGEME
74 | PLAUSIBLE_TOTP_KEY=CHANGEME
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.wants/
2 | *.service.d/*.conf
3 | profile.env
4 | .env.prod
5 | .env
6 | etc/passwords.txt
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # docker selfhosted services
2 |
3 | > with docker-compose and traefik
4 |
5 | 
6 | [](https://drone.subdavis.com/subdavis/selfhosted)
7 |
8 | This repo contains my production docker services accessible from anywhere over HTTPS using [traefik](https://traefik.io). These services (and others) run on a single server. **It used to be [rootless-mode](https://docs.docker.com/engine/security/rootless/)** but slirp4net was too slow and too much of the docker advanced configuration (permissions flags, mostly) were missing.
9 |
10 | * Jellyfin
11 | * Sonarr, Radarr, Prowlarr
12 | * Calibre Web
13 | * Kobo book downloader (kobodl)
14 | * Transmission torrent server
15 | * AdGuard Home DNS
16 | * Drone CI and runner
17 | * Duplicati
18 | * Watchtower
19 | * Cloudflare DNS Automation
20 | * Portainer
21 |
22 | # Documentation
23 |
24 | I've also written some intermediate to advanced generic usage docs for traefik, docker, pihole, and home networking. These articles are generally applicable, but some may be more useful than others.
25 |
26 | * [Configuring Wildcard Certs for Traefik](docs/wildcard-certs.md)
27 | * [LAN-only Traefik Routing with ACME SSL](docs/lan-only-routes.md)
28 | * [Configuring PiHole with dnsmasq](docs/pihole-dnsmasq.md)
29 | * [EdgeRouter Backups over SSH (SCP)](docs/edgerouter-backups.md)
30 | * [Expand LVM to fill remaining disk](docs/ubuntu-expand-lvm.md)
31 |
32 | More great documentation.
33 |
34 | * https://www.smarthomebeginner.com/traefik-2-docker-tutorial/
35 | * https://github.com/isaacrlevin/HomeNetworkSetup
36 | * https://github.com/htpcBeginner/docker-traefik
37 |
38 | ## Prerequisites
39 |
40 | * A recent version of ubuntu server with `Docker CE` installed (see below)
41 | * A router or firewall capable of dnsmasq. I use a Ubiquiti EdgeRouter X.
42 | * A domain name.
43 | * A cloudflare account.
44 |
45 | ### Home network prep
46 |
47 | * You need to make sure that ports 80 and 443 are port-forwarded through your router to whatever host this will be on.
48 | * Your server should be assigned a static private IP by DNS. `ifconfig` will list your interfaces.
49 | * Refer to the [docker-pi-hole](https://github.com/pi-hole/docker-pi-hole) docs and [my docs](docs/pihole-dnsmasq.md) for further network setup related to that service. Even though I use AdGuard Home, those docs are relevant.
50 |
51 | ### DNS Configuration
52 |
53 | **UPDATE**: This is now done automatically with [Docker Traefik Cloudflare Companion](https://github.com/tiredofit/docker-traefik-cloudflare-companion). Instructions below are left as an explanation of how this works.
54 |
55 | In this setup, each container's service will serve from a different subdomain of your Cloudflare hosted zone dyndns subdomain.
56 |
57 | * Create an `A` record for `core.mydomain.com` to point to your public IP.
58 | * For each service, you'll need to create CNAME records for each `service.mydomain.com` to point to `core.mydomain.com` because all of your services are running on the same host but the host needs to be able to do virtual host routing based on domain name.
59 | * Your services will be publically available on `https://servicename.mydomain.com`.
60 |
61 | ### Dynamic DNS (recommended)
62 |
63 | Resolving the IP address of your home network is annoying because most DNS providers change your IP every now and again. Services like No-IP combat this, but they aren't the most reliable. However, setting DNS programatically is pretty easy with Cloudflare API.
64 |
65 | * Follow the instructions in [Configuring Wildcard Certs for Traefik](docs/wildcard-certs.md) to get this part set up.
66 | * You'll need to modify `.env` with your domain info, ACME email, and cloudflare API tokens.
67 |
68 | ## Installation
69 |
70 | 1. start with ubuntu lts
71 | 1. [Enable Unattended Upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates)
72 | 1. clone this repo
73 | 1. Sign into any private docker registries
74 | 1. install [docker](https://docs.docker.com/engine/install/)
75 | a [Understanding UID remapping](https://medium.com/@tonistiigi/experimenting-with-rootless-docker-416c9ad8c0d6)
76 | a. ignore the env exports it says to set, see below
77 | 1. make sure `UsePAM yes` is set in `/etc/ssh/sshd_config` [read more](https://superuser.com/questions/1561076/systemctl-use-failed-to-connect-to-bus-no-such-file-or-directory-debian-9)
78 |
79 | ```bash
80 | cd selfhosted
81 | cp .env.example .env # edit this
82 |
83 | # make mount points
84 | mkdir /media/local /media/primary /media/secondary
85 |
86 | # install mounts
87 | systemctl link media-primary.mount
88 | systemctl link media-secondary.mount
89 |
90 | # install logrotate
91 | systenctl --user link $HOME/selfhosted/logrotate.timer
92 | systenctl --user link $HOME/selfhosted/logrotate.service
93 | systemctl --user enable logrotate.timer --now
94 |
95 | # enable traefik logrotate
96 | cp etc/traefik-logrotate.conf /etc/logrotate.d/traefik
97 |
98 | # Add to .profile
99 | # export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
100 | nano .profile
101 | ```
102 |
103 | [Set up docker daemon.json](https://forums.docker.com/t/rootless-docker-ip-range-conflicts/103341). Otherwise, you may end up with subnet ranges inside your containers that overlap with the real LAN and make hosts unreachable.
104 |
105 | ``` json
106 | {
107 | "default-address-pools": [
108 | {"base":"172.16.0.0/16","size":24},
109 | {"base":"172.20.0.0/16","size":24}
110 | ]
111 | }
112 | ```
113 |
114 | Edit `/lib/systemd/system/user@.service` to include dependencies on mounts
115 |
116 | ```conf
117 | [Unit]
118 | Requires=user-runtime-dir@%i.service media-primary.mount media-secondary.mount
119 | ```
120 |
121 | ## Automatic deployments and drone
122 |
123 | * Create a github api app. Follow drone setup instructions.
124 | * Make sure the user filtering config is set correctly so other users can't log in
125 | * Add secrets `ssh_key`, `ssh_host`, `ssh_user` for your deploy user.
126 | * Open `drone.yourdomain.com` and finish configuring your repo.
127 |
128 | ## Adguard DNS
129 |
130 | You may need to disable ubuntu's default dns service and remove resolf.conf [read more](https://www.smarthomebeginner.com/run-pihole-in-docker-on-ubuntu-with-reverse-proxy/).
131 |
132 | After disabling `systemd-resolved.service`, I ususally set a different DNS server in `/etc/resolv.conf` so that DNS doesn't break when I screw up the stack.
133 |
134 | `systemd-resolve --help` is your friend.
135 |
136 | ## WireGurad and subnet overlap
137 |
138 | * use `wg-quick` for simplicity
139 | * May need to [install or symlink resolvconf](https://superuser.com/questions/1500691/usr-bin-wg-quick-line-31-resolvconf-command-not-found-wireguard-debian)
140 | * Need to avoid [overlapping subnets](https://www.reddit.com/r/WireGuard/comments/bp01ci/connecting_to_services_through_vpn_when_the/).
141 | * Set MTU down to 1280 for issues with cellular networks, on BOTH sides of the connection.
142 | * Update: As of September 19, had to drop to 1250 for TMobile LTE to work....
143 |
144 | * My subnet is `192.168.48.0/20`
145 | * The mask is `255.255.240.0`
146 | * The default LAN will be `192.168.52.0`
147 | * The gateway is `192.168.52.1`
148 |
149 | ```
150 | Gateway: 11000000.10101000.0011 | 0100.00000001
151 | Mask: 11111111.11111111.1111 | 0000.00000000
152 | ```
153 |
154 | * The upper 4 bits will be used for VLANs (16).
155 | * The lower 8 shoud belong to a single VLAN.
156 |
157 | Using wireguard:
158 |
159 | ```bash
160 | sudo systemctl enable wg-quick@peerN --now
161 | ```
162 |
163 | I have aliases `wgup` and `wgdown` for this in my `.bashrc`.
164 |
165 | ## IPv6
166 |
167 | Some references I encountered while rolling out ipv6.
168 |
169 | [My full edgerouter config](docs/config.boot)
170 |
171 | * [Docker IPV6](https://docs.docker.com/config/daemon/ipv6/)
172 | * [Kernel modules lazy-load ip6tables](https://github.com/moby/moby/issues/33605#issuecomment-307361421)
173 | * `SYS_MODULE` capability doesn't seemt to do it. issuing an `ip6tables` dummy rule worked
174 | * [IPv6 Firewall Rules](https://community.ui.com/questions/Can-someone-let-us-know-the-added-default-IPv6-firewall-rule-mentioned-in-the-new-Edge-OS-2-01/9683f591-6cd2-4677-83c9-e90d2b7c3fbe)
175 | * Must [Block LAN to WLAN Multicast and Broadcast Data for ipv6 over wifi](https://community.ui.com/questions/IPv6-for-UniFi-WiFi/fa7109bb-c33f-4af4-9d98-dc82f0e31d99)
176 | * [You might have to disable some firewall stuff on the upstream ISP gateway](https://community.ui.com/questions/Allow-HTTPs-over-IPv6-in-firewall-Edgemax/c5f00707-4476-4b1b-91d4-7391f73aafa6)
177 | * [Disable ISP IPv6 DNS](https://kazoo.ga/dhcpv6-pd-for-native-ipv6/#)
178 | * `no-dns` in `interface` config for `rdnss`
179 | * I have not been able to get wireguard to route ipv6 traffic under slirp4netns (rootlesss). As a result, it is not possible to connect to wireguard via an ipv6 endpoint. Wireguard for ios prefers ipv4, but the desktop client prefers ipv6. Recommend creating an ipv4-only DNS record such as `wireguard4.domain.com` to force ipv4.
180 |
181 | ## Other useful nonsense
182 |
183 | ```bash
184 | # set own IP, delete set
185 | ifconfig eth0 192.168.1.5 netmask 255.255.255.0 up
186 | ifconfig en1 delete 192.168.1.5
187 | ```
188 |
189 |
190 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 | services:
3 | #############################
4 | ## ADGUARD - ROOT
5 | #############################
6 |
7 | adguard:
8 | image: adguard/adguardhome:latest
9 | container_name: adguardhome
10 | restart: unless-stopped
11 | labels:
12 | - traefik.enable=true
13 | - traefik.docker.network=adguard-net
14 | - traefik.http.services.adguard-svc.loadbalancer.server.port=80
15 | - traefik.http.routers.adguard-rtr.rule=Host(`adguard.${DNS_DOMAIN}`)
16 | - traefik.http.routers.adguard-rtr.entrypoints=websecure
17 | - traefik.http.routers.adguard-rtr.tls=true
18 | - traefik.http.routers.adguard-rtr.middlewares=ipwhitelist-mddl@docker
19 | networks:
20 | - adguard-net
21 | ports:
22 | - "53:53/udp"
23 | - "53:53/tcp"
24 | volumes:
25 | - "${LOCAL_MOUNT}/adguard/data:/opt/adguardhome/work"
26 | - "${LOCAL_MOUNT}/adguard/conf:/opt/adguardhome/conf"
27 | dns: "1.1.1.1"
28 |
29 | #############################
30 | ## ARRS
31 | #############################
32 |
33 | prowlarr:
34 | image: ghcr.io/linuxserver/prowlarr:develop
35 | container_name: prowlarr
36 | environment:
37 | - PUID=${PUID:-1000}
38 | - PGID=${PUID:-1000}
39 | - TZ=${TIME_ZONE}
40 | volumes:
41 | - ${PRIMARY_MOUNT}/prowlarr/config:/config
42 | labels:
43 | - traefik.enable=true
44 | - traefik.docker.network=arr-net
45 | - traefik.http.services.prowlarr-svc.loadbalancer.server.port=9696
46 | - traefik.http.routers.prowlarr-rtr.rule=Host(`prowlarr.${DNS_DOMAIN}`)
47 | - traefik.http.routers.prowlarr-rtr.entrypoints=websecure
48 | - traefik.http.routers.prowlarr-rtr.tls=true
49 | - traefik.http.routers.prowlarr-rtr.middlewares=ipwhitelist-mddl@docker,traefik-forward-auth@docker
50 | restart: unless-stopped
51 | networks:
52 | - arr-net
53 |
54 | radarr:
55 | image: ghcr.io/linuxserver/radarr:latest
56 | container_name: radarr
57 | restart: unless-stopped
58 | environment:
59 | - PUID=${PUID:-1000}
60 | - PGID=${PUID:-1000}
61 | - TZ=${TIME_ZONE}
62 | labels:
63 | - traefik.enable=true
64 | - traefik.docker.network=arr-net
65 | - traefik.http.services.radarr-svc.loadbalancer.server.port=7878
66 | - traefik.http.routers.radarr-rtr.rule=Host(`radarr.${DNS_DOMAIN}`)
67 | - traefik.http.routers.radarr-rtr.entrypoints=websecure
68 | - traefik.http.routers.radarr-rtr.tls=true
69 | - traefik.http.routers.radarr-rtr.middlewares=ipwhitelist-mddl@docker
70 | volumes:
71 | - ${PRIMARY_MOUNT}/radarr/data:/config
72 | - ${MEDIA_MOUNT}/plex/media/:/data
73 | networks:
74 | - arr-net
75 |
76 | sonarr:
77 | image: ghcr.io/linuxserver/sonarr
78 | container_name: sonarr
79 | restart: unless-stopped
80 | environment:
81 | - PUID=${PUID:-1000}
82 | - PGID=${PUID:-1000}
83 | - TZ=${TIME_ZONE}
84 | labels:
85 | - traefik.enable=true
86 | - traefik.docker.network=arr-net
87 | - traefik.http.services.sonarr-svc.loadbalancer.server.port=8989
88 | - traefik.http.routers.sonarr-rtr.rule=Host(`sonarr.${DNS_DOMAIN}`)
89 | - traefik.http.routers.sonarr-rtr.entrypoints=websecure
90 | - traefik.http.routers.sonarr-rtr.tls=true
91 | - traefik.http.routers.sonarr-rtr.middlewares=ipwhitelist-mddl@docker
92 | volumes:
93 | - ${PRIMARY_MOUNT}/sonarr/data:/config
94 | - ${MEDIA_MOUNT}/plex/media/:/data
95 | networks:
96 | - arr-net
97 |
98 | #############################
99 | ## CALIBRE
100 | #############################
101 |
102 | calibre_web:
103 | image: ghcr.io/linuxserver/calibre-web:latest
104 | container_name: calibre_web
105 | restart: unless-stopped
106 | labels:
107 | - traefik.enable=true
108 | - traefik.docker.network=calibre-net
109 | - traefik.http.services.calibre-svc.loadbalancer.server.port=8083
110 | - traefik.http.routers.calibre-rtr.rule=Host(`calibre.${DNS_DOMAIN}`)
111 | - traefik.http.routers.calibre-rtr.entrypoints=websecure
112 | - traefik.http.routers.calibre-rtr.tls=true
113 | - traefik.http.routers.calibre-rtr.middlewares=traefik-forward-auth@docker
114 | environment:
115 | - PUID=${PUID:-1000}
116 | - PGID=${PUID:-1000}
117 | - "TZ=${TIME_ZONE}"
118 | - "DOCKER_MODS=linuxserver/calibre-web:calibre"
119 | volumes:
120 | - ${PRIMARY_MOUNT}/calibre/config:/config
121 | - ${PRIMARY_MOUNT}/calibre/books:/books
122 | networks:
123 | - calibre-net
124 |
125 | #############################
126 | ## CHANGEDETECTION
127 | #############################
128 |
129 | changedetection:
130 | image: ghcr.io/dgtlmoon/changedetection.io
131 | container_name: changedetection
132 | restart: unless-stopped
133 | volumes:
134 | - ${SECONDARY_MOUNT}/changedetection/datastore:/datastore
135 | environment:
136 | - PUID=${PUID:-1000}
137 | - PGID=${PUID:-1000}
138 | - WEBDRIVER_URL="http://changedetection-selenium:4444/wd/hub"
139 | labels:
140 | - traefik.enable=true
141 | - traefik.docker.network=changedetection-net
142 | - traefik.http.services.changedetection-svc.loadbalancer.server.port=5000
143 | - traefik.http.routers.changedetection-rtr.rule=Host(`changedetection.${DNS_DOMAIN}`)
144 | - traefik.http.routers.changedetection-rtr.entrypoints=websecure
145 | - traefik.http.routers.changedetection-rtr.tls=true
146 | - traefik.http.routers.changedetection-rtr.middlewares=traefik-forward-auth@docker
147 | networks:
148 | - changedetection-net
149 | - changedetection-private-net
150 |
151 | changedetection-selenium:
152 | image: selenium/standalone-chrome-debug:3.141.59
153 | container_name: changedetection-selenium
154 | restart: unless-stopped
155 | volumes:
156 | # Workaround to avoid the browser crashing inside a docker container
157 | # See https://github.com/SeleniumHQ/docker-selenium#quick-start
158 | - /dev/shm:/dev/shm
159 | shm_size: '2gb'
160 | labels:
161 | - traefik.enable=false
162 | networks:
163 | - changedetection-private-net
164 |
165 | #############################
166 | ## CLOUDFLARE - ROOT
167 | #############################
168 |
169 | cloudflare:
170 | image: oznu/cloudflare-ddns:latest
171 | restart: unless-stopped
172 | container_name: cloudflare
173 | labels:
174 | - traefik.enable=false
175 | environment:
176 | - "API_KEY=${CF_TOKEN}"
177 | - "ZONE=${DNS_DOMAIN}"
178 | - "SUBDOMAIN=${SUBDOMAIN}"
179 | - "DNS_SERVER=1.0.0.1"
180 | dns: 1.1.1.1
181 |
182 | #############################
183 | ## CLOUDFLARE-COMPANION - ROOT
184 | #############################
185 |
186 | cloudflare-companion:
187 | image: tiredofit/traefik-cloudflare-companion
188 | container_name: cloudflare-companion
189 | restart: unless-stopped
190 | volumes:
191 | - ${SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock
192 | environment:
193 | - "TRAEFIK_VERSION=2"
194 | # - "CF_EMAIL=" Leave blank for scopepd
195 | - "CF_TOKEN=${CF_TOKEN}"
196 | - "TARGET_DOMAIN=${SUBDOMAIN}.${DNS_DOMAIN}"
197 | - "DOMAIN1=${DNS_DOMAIN}"
198 | - "DOMAIN1_ZONE_ID=${DNS_DOMAIN_ZONE_ID}"
199 | dns: 1.1.1.1
200 |
201 | #############################
202 | ## DRONE CI - ROOT
203 | #############################
204 |
205 | drone:
206 | image: drone/drone:1
207 | restart: unless-stopped
208 | container_name: drone
209 | user: ${PUID:-1000}
210 | labels:
211 | - traefik.enable=true
212 | - traefik.docker.network=drone-net
213 | - traefik.http.services.drone-svc.loadbalancer.server.port=80
214 | - traefik.http.routers.drone-rtr.rule=Host(`drone.${DNS_DOMAIN}`)
215 | - traefik.http.routers.drone-rtr.entrypoints=websecure
216 | - traefik.http.routers.drone-rtr.tls=true
217 | environment:
218 | - DRONE_GITHUB_CLIENT_ID=${DRONE_GITHUB_CLIENT_ID}
219 | - DRONE_GITHUB_CLIENT_SECRET=${DRONE_GITHUB_CLIENT_SECRET}
220 | - DRONE_RPC_SECRET=${DRONE_RPC_SECRET}
221 | - DRONE_SERVER_HOST=drone.${DNS_DOMAIN}
222 | - DRONE_SERVER_PROTO=https
223 | - DRONE_USER_CREATE=${DRONE_USER_CREATE}
224 | - DRONE_USER_FILTER=${DRONE_USER_FILTER}
225 | volumes:
226 | - ${PRIMARY_MOUNT}/drone/server/data:/data
227 | networks:
228 | - drone-net
229 | - drone-private-net
230 |
231 | drone-runner:
232 | image: drone/drone-runner-docker:1
233 | restart: unless-stopped
234 | container_name: drone_runner_1
235 | environment:
236 | - DRONE_RPC_PROTO=http
237 | - DRONE_RPC_HOST=drone
238 | - DRONE_RPC_SECRET=${DRONE_RPC_SECRET}
239 | - DRONE_RUNNER_CAPACITY=2
240 | - DRONE_RUNNER_NAME=drone-runner-1
241 | labels:
242 | - traefik.enable=false
243 | volumes:
244 | - ${SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock
245 | networks:
246 | - drone-private-net
247 |
248 | #############################
249 | ## DUPLICATI
250 | #############################
251 |
252 | duplicati:
253 | image: ghcr.io/linuxserver/duplicati:latest
254 | container_name: duplicati
255 | restart: unless-stopped
256 | labels:
257 | - traefik.enable=true
258 | - traefik.docker.network=duplicati-net
259 | - traefik.http.services.duplicati-svc.loadbalancer.server.port=8200
260 | - traefik.http.routers.duplicati-rtr.rule=Host(`backups.${DNS_DOMAIN}`)
261 | - traefik.http.routers.duplicati-rtr.entrypoints=websecure
262 | - traefik.http.routers.duplicati-rtr.tls=true
263 | - traefik.http.routers.duplicati-rtr.middlewares=ipwhitelist-mddl@docker,traefik-forward-auth@docker
264 | environment:
265 | - "TZ=${TIME_ZONE}"
266 | - "PUID=${XID:-1000}"
267 | - "PGID=${XID:-1000}"
268 | volumes:
269 | - ${PRIMARY_MOUNT}:/sources/primary
270 | - ${LOCAL_MOUNT}:/sources/local
271 | - ${SECONDARY_MOUNT}/duplicati/config:/config
272 | - ${SECONDARY_MOUNT}/duplicati/backups:/backups
273 | networks:
274 | - duplicati-net
275 |
276 | #############################
277 | ## JELLYFIN
278 | #############################
279 |
280 | jellyfin:
281 | image: lscr.io/linuxserver/jellyfin
282 | container_name: jellyfin
283 | restart: unless-stopped
284 | labels:
285 | - traefik.enable=true
286 | - traefik.docker.network=plex-net
287 | - traefik.http.services.jellyfin-svc.loadbalancer.server.port=8096
288 | - traefik.http.routers.jellyfin-rtr.rule=Host(`jellyfin.${DNS_DOMAIN}`)
289 | - traefik.http.routers.jellyfin-rtr.entrypoints=websecure
290 | - traefik.http.routers.jellyfin-rtr.tls=true
291 | environment:
292 | - "TZ=${TIME_ZONE}"
293 | - "PUID=${PUID:-1000}"
294 | - "PGID=${PUID:-1000}"
295 | - "JELLYFIN_PublishedServerUrl=jellyfin.${DNS_DOMAIN}"
296 | volumes:
297 | - ${PRIMARY_MOUNT}/jellyfin/config:/config
298 | - ${MEDIA_MOUNT}/plex/media:/data
299 | devices:
300 | - /dev/dri:/dev/dri
301 | networks:
302 | - plex-net
303 |
304 | #############################
305 | ## MINIFLUX
306 | #############################
307 |
308 | miniflux_postgres:
309 | image: postgres:15-alpine
310 | restart: unless-stopped
311 | container_name: miniflux_postgres
312 | volumes:
313 | - ${PRIMARY_MOUNT}/miniflux_postgres_15:/var/lib/postgresql/data
314 | environment:
315 | POSTGRES_DB: miniflux
316 | POSTGRES_USER: miniflux
317 | POSTGRES_PASSWORD: miniflux
318 | networks:
319 | - miniflux-net
320 | healthcheck:
321 | test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
322 | interval: 5s
323 | timeout: 5s
324 | retries: 5
325 |
326 | miniflux:
327 | image: ghcr.io/miniflux/miniflux:latest
328 | container_name: miniflux
329 | restart: unless-stopped
330 | depends_on:
331 | miniflux_postgres:
332 | condition: service_healthy
333 | healthcheck:
334 | test: ["CMD", "/usr/bin/miniflux", "-healthcheck", "auto"]
335 | interval: 10s
336 | timeout: 5s
337 | retries: 5
338 | environment:
339 | - "BASE_URL=https://miniflux.${DNS_DOMAIN}"
340 | - "DATABASE_URL=postgres://miniflux:miniflux@miniflux_postgres/miniflux?sslmode=disable"
341 | - RUN_MIGRATIONS=1
342 | - CREATE_ADMIN=1
343 | - ADMIN_USERNAME=admin
344 | - ADMIN_PASSWORD=miniflux
345 | volumes:
346 | - ${PRIMARY_MOUNT}/miniflux/config:/config
347 | labels:
348 | - traefik.enable=true
349 | - traefik.docker.network=miniflux-net
350 | - traefik.http.services.miniflux-svc.loadbalancer.server.port=8080
351 | - traefik.http.routers.miniflux-rtr.rule=Host(`miniflux.${DNS_DOMAIN}`)
352 | - traefik.http.routers.miniflux-rtr.entrypoints=websecure
353 | - traefik.http.routers.miniflux-rtr.tls=true
354 | networks:
355 | - miniflux-net
356 |
357 | #############################
358 | ## KOBODL
359 | #############################
360 |
361 | kobodl:
362 | image: ghcr.io/subdavis/kobodl:latest
363 | container_name: kobodl
364 | restart: unless-stopped
365 | user: ${PUID:-1000}
366 | labels:
367 | - traefik.enable=true
368 | - traefik.docker.network=kobodl-net
369 | - traefik.http.services.kobodl-svc.loadbalancer.server.port=5000
370 | - traefik.http.routers.kobodl-rtr.rule=Host(`kobodl.${DNS_DOMAIN}`)
371 | - traefik.http.routers.kobodl-rtr.entrypoints=websecure
372 | - traefik.http.routers.kobodl-rtr.tls=true
373 | - traefik.http.routers.kobodl-rtr.middlewares=traefik-forward-auth@docker
374 | volumes:
375 | - ${PRIMARY_MOUNT}/kobodl/kobodl.json:/home/kobodl.json
376 | - ${PRIMARY_MOUNT}/kobodl/downloads:/home/downloads
377 | command: --config /home/kobodl.json serve -h 0.0.0.0 --output-dir /home/downloads
378 | networks:
379 | - kobodl-net
380 |
381 | #############################
382 | ## PORTAINER - ROOT
383 | #############################
384 |
385 | portainer:
386 | image: portainer/portainer-ce:latest
387 | restart: unless-stopped
388 | container_name: portainer
389 | labels:
390 | - traefik.enable=true
391 | - traefik.docker.network=portainer-net
392 | - traefik.http.services.portainer-svc.loadbalancer.server.port=9000
393 | - traefik.http.routers.portainer-rtr.rule=Host(`portainer.${DNS_DOMAIN}`)
394 | - traefik.http.routers.portainer-rtr.entrypoints=websecure
395 | - traefik.http.routers.portainer-rtr.tls=true
396 | - traefik.http.routers.portainer-rtr.middlewares=ipwhitelist-mddl@docker,traefik-forward-auth@docker
397 | volumes:
398 | - ${PRIMARY_MOUNT}/portainer/data/:/data
399 | - ${SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock
400 | networks:
401 | - portainer-net
402 |
403 | #############################
404 | ## RSSHUB
405 | #############################
406 |
407 | rsshub:
408 | image: diygod/rsshub:chromium-bundled
409 | container_name: rsshub
410 | restart: always
411 | user: ${PUID:-1000}
412 | environment:
413 | NODE_ENV: production
414 | CACHE_TYPE: redis
415 | REDIS_URL: "redis://rsshub_redis:6379/"
416 | labels:
417 | - traefik.enable=true
418 | - traefik.docker.network=rsshub-net
419 | - traefik.http.services.rsshub-svc.loadbalancer.server.port=1200
420 | - traefik.http.routers.rsshub-rtr.rule=Host(`rsshub.${DNS_DOMAIN}`)
421 | - traefik.http.routers.rsshub-rtr.entrypoints=websecure
422 | - traefik.http.routers.rsshub-rtr.tls=true
423 | depends_on:
424 | - redis
425 | networks:
426 | - rsshub-net
427 | - rsshub-private-net
428 |
429 | redis:
430 | image: redis:alpine
431 | restart: always
432 | container_name: rsshub_redis
433 | volumes:
434 | - redis-data:/data
435 | healthcheck:
436 | test: ["CMD", "redis-cli", "ping"]
437 | interval: 30s
438 | timeout: 10s
439 | retries: 5
440 | start_period: 5s
441 | networks:
442 | - rsshub-private-net
443 |
444 | #############################
445 | ## TRANSMISSION_TORRENT
446 | #############################
447 |
448 | transmission:
449 | image: haugene/transmission-openvpn:4
450 | container_name: transmission
451 | restart: unless-stopped
452 | labels:
453 | - "com.centurylinklabs.watchtower.enable=false"
454 | - traefik.enable=true
455 | - traefik.docker.network=transmission-net
456 | - traefik.http.services.transmission-svc.loadbalancer.server.port=9091
457 | - traefik.http.routers.transmission-rtr.rule=Host(`torrent.${DNS_DOMAIN}`)
458 | - traefik.http.routers.transmission-rtr.entrypoints=websecure
459 | - traefik.http.routers.transmission-rtr.tls=true
460 | - traefik.http.routers.transmission-rtr.middlewares=traefik-forward-auth@docker,ipwhitelist-mddl@docker
461 | networks:
462 | - arr-net
463 | - transmission-net
464 | dns: 1.1.1.1
465 | sysctls:
466 | - net.ipv6.conf.all.disable_ipv6=1
467 | cap_add:
468 | - NET_ADMIN
469 | volumes:
470 | - ${MEDIA_MOUNT}/plex/media/:/data
471 | - /etc/localtime:/etc/localtime:ro
472 | environment:
473 | - PUID=${PUID:-1000}
474 | - PGID=${PUID:-1000}
475 | - "OPENVPN_CONFIG=${OPENVPN_CONFIG}"
476 | - "OPENVPN_PROVIDER=${OPENVPN_PROVIDER}"
477 | - "OPENVPN_USERNAME=${TORRENT_USERNAME}"
478 | - "OPENVPN_PASSWORD=${TORRENT_PASSWORD}"
479 | - "OPENVPN_OPTS=--inactive 3600 --ping 10 --ping-exit 60"
480 | - "LOCAL_NETWORK=${LOCAL_NETWORK}"
481 | - "PIA_OPENVPN_CONFIG_BUNDLE=openvpn-tcp"
482 |
483 | #############################
484 | ## TRAEFIK - ROOT
485 | #############################
486 |
487 | traefik:
488 | image: traefik:v2.4
489 | restart: unless-stopped
490 | container_name: traefik
491 | command: >
492 | --api.insecure=true
493 | --serversTransport.insecureSkipVerify=true
494 | --accesslog=true
495 | --accesslog.filepath=/var/log/traefik/access.log
496 | --accesslog.fields.headers.names.Content-Type=keep
497 | --accesslog.fields.headers.names.Referer=keep
498 | --accesslog.fields.headers.names.User-Agent=keep
499 | --providers.docker=true
500 | --providers.docker.exposedByDefault=false
501 | --entrypoints.web.address=:80
502 | --entrypoints.websecure.address=:443
503 | --entrypoints.websecure.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/12,172.64.0.0/13,131.0.72.0/22
504 | --entrypoints.websecure.http.tls.certresolver=myresolver
505 | --entrypoints.websecure.http.tls.domains[0].main=*.${DNS_DOMAIN}
506 | --entrypoints.websecure.http.tls.domains[0].sans=${DNS_DOMAIN}
507 | --certificatesResolvers.myresolver.acme.caServer="https://acme-v02.api.letsencrypt.org/directory"
508 | --certificatesresolvers.myresolver.acme.dnschallenge=true
509 | --certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare
510 | --certificatesresolvers.myresolver.acme.email="${LETSENCRYPT_EMAIL}"
511 | --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
512 | --certificatesresolvers.myresolver.acme.dnschallenge.resolvers==1.1.1.1:53,1.0.0.1:53
513 | labels:
514 | - "traefik.enable=true"
515 | # Traefik HTTPS Redirect
516 | - "traefik.http.routers.http-catchall.entrypoints=web"
517 | - "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
518 | - "traefik.http.routers.http-catchall.middlewares=redirect-to-https-mddl@docker"
519 | - "traefik.http.middlewares.redirect-to-https-mddl.redirectscheme.scheme=https"
520 | # Other middlewares
521 | - "traefik.http.middlewares.ipwhitelist-mddl.ipwhitelist.sourcerange=127.0.0.1/32,${SUBNET}"
522 | - "traefik.http.middlewares.traefik-forward-auth.forwardauth.address=http://auth:4181"
523 | - "traefik.http.middlewares.traefik-forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User"
524 | # For Vaultwarden
525 | - "traefik.http.middlewares.bw-stripPrefix.stripprefix.prefixes=/notifications/hub"
526 | - "traefik.http.middlewares.bw-stripPrefix.stripprefix.forceSlash=false"
527 | # Traefik Dashboard config
528 | - traefik.http.services.traefik-svc.loadbalancer.server.port=8080
529 | - traefik.http.routers.traefik-rtr.rule=Host(`traefik.${DNS_DOMAIN}`)
530 | - traefik.http.routers.traefik-rtr.entrypoints=websecure
531 | - traefik.http.routers.traefik-rtr.tls=true
532 | - traefik.http.routers.traefik-rtr.middlewares=traefik-forward-auth@docker
533 | environment:
534 | - CF_API_EMAIL=${CF_EMAIL}
535 | - CF_DNS_API_TOKEN=${CF_TOKEN}
536 | - CF_ZONE_API_TOKEN=${CF_TOKEN}
537 | volumes:
538 | - ${SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock
539 | - "${LOCAL_MOUNT}/traefik/letsencrypt:/letsencrypt"
540 | - "${LOCAL_MOUNT}/traefik/logs:/var/log/traefik"
541 | - "./etc/traefik:/etc/traefik"
542 | ports:
543 | - "80:80"
544 | - "443:443"
545 | networks:
546 | - traefik-net
547 | - adguard-net
548 | - arr-net
549 | - auth-net
550 | - calibre-net
551 | - changedetection-net
552 | - drone-net
553 | - duplicati-net
554 | - miniflux-net
555 | - kobodl-net
556 | - plex-net
557 | - portainer-net
558 | - rsshub-net
559 | - transmission-net
560 | - umami-net
561 | - unifi-net
562 | - vaultwarden-net
563 | - webdav-net
564 |
565 | traefik-forward-auth:
566 | image: thomseddon/traefik-forward-auth:latest
567 | container_name: auth
568 | restart: unless-stopped
569 | user: ${PUID:-1000}
570 | networks:
571 | - auth-net
572 | environment:
573 | - "PROVIDERS_GOOGLE_CLIENT_ID=${AUTH_PROVIDERS_GOOGLE_CLIENT_ID}"
574 | - "PROVIDERS_GOOGLE_CLIENT_SECRET=${AUTH_PROVIDERS_GOOGLE_CLIENT_SECRET}"
575 | - "SECRET=${AUTH_SECRET}"
576 | - "WHITELIST=${AUTH_WHITELIST}"
577 | command: >
578 | --cookie-domain="${DNS_DOMAIN}"
579 | --auth-host="auth.${DNS_DOMAIN}"
580 | --config=/etc/authconfig.ini
581 | volumes:
582 | - "./etc/authconfig.ini:/etc/authconfig.ini:ro"
583 | labels:
584 | - traefik.enable=true
585 | - traefik.docker.network=auth-net
586 | - traefik.http.services.traefik-forward-auth-svc.loadbalancer.server.port=4181
587 | - traefik.http.routers.auth-rtr.rule=Host(`auth.${DNS_DOMAIN}`)
588 | - traefik.http.routers.auth-rtr.entrypoints=websecure
589 | - traefik.http.routers.auth-rtr.tls=true
590 | - traefik.http.routers.auth-rtr.middlewares=traefik-forward-auth
591 |
592 | #############################
593 | ## Umami
594 | #############################
595 |
596 | umami_postgres:
597 | image: postgres:15-alpine
598 | restart: always
599 | container_name: umami_postgres
600 | volumes:
601 | - ${PRIMARY_MOUNT}/postgres_15:/var/lib/postgresql/data
602 | environment:
603 | POSTGRES_DB: umami
604 | POSTGRES_USER: umami
605 | POSTGRES_PASSWORD: umami
606 | networks:
607 | - umami-private-net
608 | healthcheck:
609 | test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
610 | interval: 5s
611 | timeout: 5s
612 | retries: 5
613 |
614 | umami:
615 | image: ghcr.io/umami-software/umami:postgresql-latest
616 | restart: always
617 | container_name: umami
618 | user: ${PUID:-1000}
619 | depends_on:
620 | umami_postgres:
621 | condition: service_healthy
622 | environment:
623 | DATABASE_URL: postgresql://umami:umami@umami_postgres:5432/umami
624 | DATABASE_TYPE: postgresql
625 | APP_SECRET: ${DNS_DOMAIN_ZONE_ID}
626 | labels:
627 | - traefik.enable=true
628 | - traefik.docker.network=umami-net
629 | - traefik.http.services.umami-svc.loadbalancer.server.port=3000
630 | - traefik.http.routers.umami-rtr.rule=Host(`umami.${DNS_DOMAIN}`)
631 | - traefik.http.routers.umami-rtr.entrypoints=websecure
632 | - traefik.http.routers.umami-rtr.tls=true
633 | networks:
634 | - umami-net
635 | - umami-private-net
636 | healthcheck:
637 | test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"]
638 | interval: 5s
639 | timeout: 5s
640 | retries: 5
641 |
642 | #############################
643 | ## UNIFI CONTROLLER
644 | #############################
645 |
646 | unifi_controller:
647 | image: ghcr.io/linuxserver/unifi-controller:latest
648 | container_name: unifi_controller
649 | restart: unless-stopped
650 | environment:
651 | - PUID=${PUID:-1000}
652 | - PGID=${PUID:-1000}
653 | - MEM_LIMIT=1024
654 | ports:
655 | - "3478:3478/udp" # STUN
656 | - "10001:10001/udp" # Discovery
657 | - "8080:8080" # Device comms
658 | - "6789:6789" # Mobile speedtest
659 | labels:
660 | - traefik.enable=true
661 | - traefik.docker.network=unifi-net
662 | - traefik.http.routers.ubiq-rtr.rule=Host(`unifi.${DNS_DOMAIN}`)
663 | - traefik.http.routers.ubiq-rtr.entrypoints=websecure
664 | - traefik.http.routers.ubiq-rtr.tls=true
665 | - traefik.http.routers.ubiq-rtr.middlewares=ipwhitelist-mddl@docker
666 | - traefik.http.services.ubiq-svc.loadbalancer.server.scheme=https
667 | - traefik.http.services.ubiq-svc.loadbalancer.server.port=8443
668 | volumes:
669 | - "${PRIMARY_MOUNT}/unifi/config/:/config"
670 | networks:
671 | - unifi-net
672 |
673 | #############################
674 | ## VAULTWARDEN
675 | #############################
676 |
677 | vaultwarden:
678 | image: vaultwarden/server:latest
679 | container_name: vaultwarden
680 | restart: always
681 | user: ${PUID:-1000}
682 | volumes:
683 | - "${PRIMARY_MOUNT}/vaultwarden/data/:/data"
684 | environment:
685 | - WEBSOCKET_ENABLED=true
686 | networks:
687 | - vaultwarden-net
688 | labels:
689 | - "traefik.enable=true"
690 | - "traefik.docker.network=vaultwarden-net"
691 | # Entry Point for https
692 | - "traefik.http.routers.vaultwarden-rtr.entrypoints=websecure"
693 | - "traefik.http.routers.vaultwarden-rtr.rule=Host(`vaultwarden.${DNS_DOMAIN}`)"
694 | - "traefik.http.routers.vaultwarden-rtr.service=vaultwarden-svc"
695 | - "traefik.http.services.vaultwarden-svc.loadbalancer.server.port=80"
696 | # websocket
697 | - "traefik.http.routers.vaultwarden-ws-rtr.entrypoints=websecure"
698 | - "traefik.http.routers.vaultwarden-ws-rtr.rule=Host(`vaultwarden.${DNS_DOMAIN}`) && Path(`/notifications/hub`)"
699 | - "traefik.http.middlewares.vaultwarden-ws-rtr=bw-stripPrefix@file"
700 | - "traefik.http.routers.vaultwarden-ws-rtr.service=vaultwarden-ws-svc"
701 | - "traefik.http.services.vaultwarden-ws-svc.loadbalancer.server.port=3012"
702 |
703 | #############################
704 | ## WEBDAV
705 | #############################
706 |
707 | webdav:
708 | image: bytemark/webdav
709 | container_name: webdav
710 | restart: always
711 | environment:
712 | AUTH_TYPE: Digest
713 | USERNAME: admin
714 | PASSWORD: password
715 | volumes:
716 | - "${PRIMARY_MOUNT}/webdav:/var/lib/dav"
717 | networks:
718 | - webdav-net
719 | labels:
720 | - "traefik.enable=true"
721 | - "traefik.docker.network=webdav-net"
722 | - "traefik.http.routers.webdav-rtr.entrypoints=websecure"
723 | - "traefik.http.routers.webdav-rtr.rule=Host(`webdav.${DNS_DOMAIN}`)"
724 | - "traefik.http.routers.webdav-rtr.service=webdav-svc"
725 | - "traefik.http.services.webdav-svc.loadbalancer.server.port=80"
726 | - "traefik.http.routers.webdav-rtr.middlewares=ipwhitelist-mddl@docker"
727 |
728 | #############################
729 | ## WATCHTOWER - ROOT
730 | #############################
731 |
732 | watchtower:
733 | image: containrrr/watchtower:latest
734 | container_name: watchtower
735 | restart: unless-stopped
736 | command: --schedule "0 10 3 * * *" --cleanup
737 | labels:
738 | - traefik.enable=false
739 | volumes:
740 | - ${SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock
741 | - "${PRIMARY_MOUNT}/watchtower/config/:/config"
742 | - "${PRIMARY_MOUNT}/watchtower/docker-config.json:/config.json"
743 |
744 | networks:
745 | adguard-net:
746 | name: adguard-net
747 | arr-net:
748 | name: arr-net
749 | auth-net:
750 | name: auth-net
751 | calibre-net:
752 | name: calibre-net
753 | changedetection-net:
754 | name: changedetection-net
755 | changedetection-private-net:
756 | name: changedetection-private-net
757 | drone-net:
758 | name: drone-net
759 | drone-private-net:
760 | name: drone-private-net
761 | duplicati-net:
762 | name: duplicati-net
763 | miniflux-net:
764 | name: miniflux-net
765 | kobodl-net:
766 | name: kobodl-net
767 | plex-net:
768 | name: plex-net
769 | portainer-net:
770 | name: portainer-net
771 | rsshub-net:
772 | name: rsshub-net
773 | rsshub-private-net:
774 | name: rsshub-private-net
775 | transmission-net:
776 | name: transmission-net
777 | traefik-net:
778 | name: traefik-net
779 | umami-net:
780 | name: umami-net
781 | umami-private-net:
782 | name: umami-private-net
783 | unifi-net:
784 | name: unifi-net
785 | vaultwarden-net:
786 | name: vaultwarden-net
787 | webdav-net:
788 | name: webdav-net
789 |
790 | volumes:
791 | redis-data:
792 |
--------------------------------------------------------------------------------
/docs/config.boot:
--------------------------------------------------------------------------------
1 | firewall {
2 | all-ping enable
3 | broadcast-ping disable
4 | group {
5 | address-group DNS-Servers {
6 | address 192.168.52.175
7 | address 192.168.52.112
8 | }
9 | }
10 | ipv6-name WANv6_IN {
11 | default-action drop
12 | description "WAN inbound traffic forwarded to LAN"
13 | enable-default-log
14 | rule 10 {
15 | action accept
16 | description "Allow established/related sessions"
17 | state {
18 | established enable
19 | related enable
20 | }
21 | }
22 | rule 20 {
23 | action drop
24 | description "Drop invalid state"
25 | state {
26 | invalid enable
27 | }
28 | }
29 | rule 201 {
30 | action accept
31 | description "icmpv6 destination-unreachable"
32 | log enable
33 | protocol ipv6-icmp
34 | }
35 | rule 300 {
36 | action accept
37 | description Traefik
38 | destination {
39 | address ::8e89:a5ff:fe3b:3b41/::ffff:ffff:ffff:ffff # This is a neat trick
40 | port 80,443
41 | }
42 | protocol tcp
43 | }
44 | rule 301 {
45 | action accept
46 | description Wireguard
47 | destination {
48 | address ::8e89:a5ff:fe3b:3b41/::ffff:ffff:ffff:ffff
49 | port 51820
50 | }
51 | protocol tcp_udp
52 | }
53 | }
54 | ipv6-name WANv6_LOCAL {
55 | default-action drop
56 | description "WAN inbound traffic to the router"
57 | enable-default-log
58 | rule 10 {
59 | action accept
60 | description "Allow established/related sessions"
61 | state {
62 | established enable
63 | related enable
64 | }
65 | }
66 | rule 20 {
67 | action drop
68 | description "Drop invalid state"
69 | state {
70 | invalid enable
71 | }
72 | }
73 | rule 30 {
74 | action accept
75 | description "Allow IPv6 icmp"
76 | protocol ipv6-icmp
77 | }
78 | rule 40 {
79 | action accept
80 | description "allow dhcpv6"
81 | destination {
82 | port 546
83 | }
84 | protocol udp
85 | source {
86 | port 547
87 | }
88 | }
89 | }
90 | ipv6-receive-redirects disable
91 | ipv6-src-route disable
92 | ip-src-route disable
93 | log-martians enable
94 | name WAN_IN {
95 | default-action drop
96 | description "WAN to internal"
97 | rule 10 {
98 | action accept
99 | description "Allow established/related"
100 | state {
101 | established enable
102 | related enable
103 | }
104 | }
105 | rule 20 {
106 | action drop
107 | description "Drop invalid state"
108 | state {
109 | invalid enable
110 | }
111 | }
112 | }
113 | name WAN_LOCAL {
114 | default-action drop
115 | description "WAN to router"
116 | rule 10 {
117 | action accept
118 | description "Allow established/related"
119 | state {
120 | established enable
121 | related enable
122 | }
123 | }
124 | rule 20 {
125 | action drop
126 | description "Drop invalid state"
127 | state {
128 | invalid enable
129 | }
130 | }
131 | }
132 | receive-redirects disable
133 | send-redirects enable
134 | source-validation disable
135 | syn-cookies enable
136 | }
137 | interfaces {
138 | ethernet eth0 {
139 | address dhcp
140 | description Internet
141 | dhcp-options {
142 | default-route update
143 | default-route-distance 210
144 | name-server no-update
145 | }
146 | dhcpv6-pd {
147 | no-dns
148 | pd 0 {
149 | interface switch0 {
150 | no-dns
151 | service slaac
152 | }
153 | prefix-length /64
154 | }
155 | rapid-commit enable
156 | }
157 | duplex auto
158 | firewall {
159 | in {
160 | ipv6-name WANv6_IN
161 | name WAN_IN
162 | }
163 | local {
164 | ipv6-name WANv6_LOCAL
165 | name WAN_LOCAL
166 | }
167 | out {
168 | }
169 | }
170 | ipv6 {
171 | address {
172 | autoconf
173 | }
174 | dup-addr-detect-transmits 1
175 | }
176 | pppoe 0 {
177 | default-route auto
178 | ipv6 {
179 | address {
180 | autoconf
181 | }
182 | dup-addr-detect-transmits 1
183 | enable {
184 | }
185 | }
186 | mtu 1492
187 | name-server auto
188 | }
189 | speed auto
190 | }
191 | ethernet eth1 {
192 | description Local
193 | duplex auto
194 | speed auto
195 | }
196 | ethernet eth2 {
197 | description Local
198 | duplex auto
199 | speed auto
200 | }
201 | ethernet eth3 {
202 | description Local
203 | duplex auto
204 | speed auto
205 | }
206 | ethernet eth4 {
207 | description Local
208 | duplex auto
209 | poe {
210 | output off
211 | }
212 | speed auto
213 | }
214 | loopback lo {
215 | }
216 | switch switch0 {
217 | address 192.168.52.1/20
218 | description Local
219 | mtu 1500
220 | switch-port {
221 | interface eth1 {
222 | }
223 | interface eth2 {
224 | }
225 | interface eth3 {
226 | }
227 | vlan-aware disable
228 | }
229 | }
230 | }
231 | port-forward {
232 | auto-firewall enable
233 | hairpin-nat enable
234 | lan-interface switch0
235 | rule 1 {
236 | description http
237 | forward-to {
238 | address 192.168.52.175
239 | port 80
240 | }
241 | original-port http
242 | protocol tcp
243 | }
244 | rule 2 {
245 | description https
246 | forward-to {
247 | address 192.168.52.175
248 | port 443
249 | }
250 | original-port https
251 | protocol tcp
252 | }
253 | rule 3 {
254 | description wireguard
255 | forward-to {
256 | address 192.168.52.175
257 | port 51820
258 | }
259 | original-port 51820
260 | protocol tcp_udp
261 | }
262 | wan-interface eth0
263 | }
264 | service {
265 | dhcp-server {
266 | disabled false
267 | hostfile-update disable
268 | shared-network-name lan {
269 | authoritative disable
270 | subnet 192.168.52.0/24 {
271 | default-router 192.168.52.1
272 | lease 86400
273 | start 192.168.52.38 {
274 | stop 192.168.52.243
275 | }
276 | static-mapping Draynor {
277 | ip-address 192.168.52.112
278 | mac-address b8:27:eb:b3:9f:ce
279 | }
280 | static-mapping MainAP {
281 | ip-address 192.168.52.185
282 | mac-address e0:63:da:3c:68:be
283 | }
284 | static-mapping PhilipsHue {
285 | ip-address 192.168.52.230
286 | mac-address 00:17:88:6d:95:69
287 | }
288 | static-mapping lumbridge {
289 | ip-address 192.168.52.175
290 | mac-address 8c:89:a5:3b:3b:41
291 | }
292 | static-mapping varrock {
293 | ip-address 192.168.52.40
294 | mac-address 54:bf:64:6c:91:e9
295 | }
296 | static-mapping wildy {
297 | ip-address 192.168.52.45
298 | mac-address 00:23:24:64:4b:98
299 | }
300 | unifi-controller 192.168.52.175
301 | }
302 | }
303 | static-arp disable
304 | use-dnsmasq enable
305 | }
306 | dns {
307 | dynamic {
308 | interface eth0 {
309 | service dyndns {
310 | host-name subdavis
311 | login nouser
312 | password ********
313 | protocol dyndns2
314 | server www.duckdns.org
315 | }
316 | web dyndns
317 | }
318 | }
319 | forwarding {
320 | cache-size 150
321 | listen-on switch0
322 | name-server 192.168.52.175
323 | options dhcp-option=6,192.168.52.1
324 | options bind-interfaces
325 | options listen-address=127.0.0.1
326 | options listen-address=192.168.52.1
327 | }
328 | }
329 | gui {
330 | http-port 80
331 | https-port 443
332 | older-ciphers enable
333 | }
334 | nat {
335 | rule 1002 {
336 | description "Redirect DNS"
337 | destination {
338 | port 53
339 | }
340 | inbound-interface switch0
341 | inside-address {
342 | address 192.168.52.175
343 | port 53
344 | }
345 | log disable
346 | protocol tcp_udp
347 | source {
348 | group {
349 | address-group !DNS-Servers
350 | }
351 | }
352 | type destination
353 | }
354 | rule 5002 {
355 | description "Translate DNS to Internal reply"
356 | destination {
357 | group {
358 | address-group DNS-Servers
359 | }
360 | port 53
361 | }
362 | log disable
363 | outbound-interface switch0
364 | protocol tcp_udp
365 | type masquerade
366 | }
367 | rule 5010 {
368 | description "masquerade for WAN"
369 | outbound-interface eth0
370 | type masquerade
371 | }
372 | }
373 | ssh {
374 | port 22
375 | protocol-version v2
376 | }
377 | unms {
378 | disable
379 | }
380 | }
381 | system {
382 | config-management {
383 | commit-archive {
384 | }
385 | }
386 | domain-name lan
387 | host-name ubnt
388 | login {
389 | user ubnt {
390 | authentication {
391 | encrypted-password ********
392 | plaintext-password ""
393 | }
394 | full-name ""
395 | level admin
396 | }
397 | }
398 | name-server 127.0.0.1
399 | ntp {
400 | server 0.ubnt.pool.ntp.org {
401 | }
402 | server 1.ubnt.pool.ntp.org {
403 | }
404 | server 2.ubnt.pool.ntp.org {
405 | }
406 | server 3.ubnt.pool.ntp.org {
407 | }
408 | }
409 | offload {
410 | hwnat enable
411 | }
412 | package {
413 | repository stretch {
414 | components "main contrib non-free"
415 | distribution stretch
416 | password ""
417 | url http://http.us.debian.org/debian
418 | username ""
419 | }
420 | }
421 | syslog {
422 | global {
423 | facility all {
424 | level notice
425 | }
426 | facility protocols {
427 | level debug
428 | }
429 | }
430 | }
431 | task-scheduler {
432 | }
433 | time-zone America/New_York
434 | }
435 |
--------------------------------------------------------------------------------
/docs/edgerouter-backups.md:
--------------------------------------------------------------------------------
1 | # Backing up EdgeRouter X
2 |
3 | On commit, save the router config to an external server
4 |
5 | * scp will push the file to the remote over ssh.
6 | * SSH keys don't work, so must use password.
7 | * Create a user `edgerouter-backup` on the target host.
8 | * [Only allow password login for a specific user](https://serverfault.com/questions/307407/ssh-allow-password-for-one-user-rest-only-allow-public-keys)
9 | * [Set Up automatic backups](https://help.ui.com/hc/en-us/articles/204960084-EdgeMAX-Manage-the-configuration-file)
10 |
11 | ``` bash
12 | configure
13 | set system config-management commit-archive location "scp://edgerouter-backup:password@host.lan/path/to/parent"
14 | commit ; save
15 | ```
16 |
17 | Now you can back up the local files with duplicati.
18 |
--------------------------------------------------------------------------------
/docs/images/cfapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/subdavis/selfhosted/36d9968a684d1c437207bef9a7c71d1a4786f719/docs/images/cfapi.png
--------------------------------------------------------------------------------
/docs/images/pihole-advanced.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/subdavis/selfhosted/36d9968a684d1c437207bef9a7c71d1a4786f719/docs/images/pihole-advanced.png
--------------------------------------------------------------------------------
/docs/images/pihole-dns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/subdavis/selfhosted/36d9968a684d1c437207bef9a7c71d1a4786f719/docs/images/pihole-dns.png
--------------------------------------------------------------------------------
/docs/images/router-dhcp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/subdavis/selfhosted/36d9968a684d1c437207bef9a7c71d1a4786f719/docs/images/router-dhcp.png
--------------------------------------------------------------------------------
/docs/images/router-dnsmasq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/subdavis/selfhosted/36d9968a684d1c437207bef9a7c71d1a4786f719/docs/images/router-dnsmasq.png
--------------------------------------------------------------------------------
/docs/images/router-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/subdavis/selfhosted/36d9968a684d1c437207bef9a7c71d1a4786f719/docs/images/router-system.png
--------------------------------------------------------------------------------
/docs/lan-only-routes.md:
--------------------------------------------------------------------------------
1 | # LAN-only routes with Traefik
2 |
3 | > **GOAL:** In this guide, you'll learn how to set up portainer with valid SSL and Host-based routing privately on your LAN.
4 | >
5 | > Turn _this_: `http://myserver.lan:8080`
6 | >
7 | > Into this: `https://portainer.mydomain.com`
8 | >
9 | > ...all while keepoing your private services off the internet.
10 |
11 | When running services in traefik, you'll likely want to expose some to the internet (like plex) and keep others accessible only from your local network (like portainer). This document is mostly about IP whitelisting, but I want to first talk about SSL and security.
12 |
13 | ## SSL for private routes
14 |
15 | There are 2 main ways of creating [routing rules](https://docs.traefik.io/routing/routers/#rule) for apps: Host rules and PathPrefix rules.
16 |
17 | * Host rules route based on the hostname of the destination, like `foo.mydomain.com` or `http://192.168.0.10`.
18 | * PathPrefix rules route based on some prefix substring, like `/plex` or `/portainer`.
19 |
20 | You may think that, without rolling out some robust DNS on your home network, you're stuck with `http://myserver.lan/portainer` as your best option for routing.
21 |
22 | This has drawbacks:
23 |
24 | 1. PathPrefix rules are an **enormous pain in the ass** because [nobody understands how they should work](https://github.com/elastic/kibana/issues/6665).
25 | 1. Without a legitimate domain name, you're stuck with self-signed certificates.
26 |
27 | Instead, I like to use my real domain even for local routes.
28 |
29 | 1. Create a CNAME or A record for `portainer.mydomain.com` to point to either your server's private IP or even your network's public IP. **NOTE:** if you're using unbound DNS or a DNS resolver that blocks resolution for private addresses, you'll have to use your public IP and set up port forwarding even if you block all public addresses. Might be possible to do some NAT magic to avoid this.
30 | 1. Set up [Wildcard SSL for your domain](wildcard-certs.md)
31 |
32 | Now, you'd be ready to set up a publicly accessible service, except we're going to restrict access.
33 |
34 | ## IP Whitelisting
35 |
36 | ### The Traefik Part
37 |
38 | With the IPWhitelist middleware, we're going to restrict access to your LAN subnet. You can run traefik exactly the same as in [the wildcard SSL tutorial](wildcard-certs.md).
39 |
40 | * If you choose to use your **Public** IP, you still need to set up port forwarding on your router for this to work correctly. This is because requests to `portainer.mydomain.com` will resolve to your public IP, get routed to your public interface, then directed back into your LAN, so the actual external filtering will happen on your traefik host.
41 | * If you choose to use your **Private** IP in your DNS records, you don't need port forwarding becasue the request will never hit your router. Beware that certain configurations of Unbound DNS will block DNS resolution to private IPs (if you don't know what this is, you're probably not affected).
42 |
43 | ### The Portainer Part
44 |
45 | This part will also be almost the same as the wildcard tutorial, with the addition of 1 middleware. Refer to [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) if you don't know how to represent your subnet as a CIDR block.
46 |
47 | ``` bash
48 | export MY_APP_DOMAIN=mydomain.com
49 | export SUBNET="192.168.0.0/24"
50 | docker run --rm --name portainer \
51 | --label traefik.enable=true \
52 | --label traefik.http.services.my-service.loadbalancer.server.port="9000" \
53 | --label traefik.http.middlewares.middleware-redirect-https.redirectscheme.scheme="https" \
54 | --label traefik.http.routers.my-route.entrypoints=web \
55 | --label traefik.http.routers.my-route.rule="Host(`portainer.${MY_APP_DOMAIN}`)" \
56 | --label traefik.http.routers.my-route.middlewares="middleware-redirect-https@docker" \
57 | --label traefik.http.routers.my-route-secure.entrypoints=websecure \
58 | --label traefik.http.routers.my-route-secure.rule="Host(`portainer.${MY_APP_DOMAIN}`)" \
59 | --label traefik.http.routers.my-route-secure.tls.domains[0].main="*.${MY_APP_DOMAIN}" \
60 | --label traefik.http.routers.my-route-secure.tls.certresolver="myresolver" \
61 | --label traefik.http.routers.my-route-secure.tls=true \
62 | --label traefik.http.middlewares.middleware-ipwhitelist.ipwhitelist.sourcerange="127.0.0.1/32,${SUBNET}" \
63 | --label traefik.http.routers.%N-secure.middlewares="middleware-ipwhitelist@docker" \
64 | --volume /tmp/portainer/data/:/data \
65 | --volume /var/run/docker.sock:/var/run/docker.sock \
66 | portainer/portainer
67 | ```
68 |
69 | ## Testing it
70 |
71 | Visit https://portainer.mydomain.com
72 |
73 | It doesn't matter whether your DNS record for `portainer.mydomain.com` points at your network's Public IP or the traefik server's private IP. When the request hits your Firewall or router, you'll get redirected internally and traefik will examine the origin of the request, which will be your host's private IP.
74 |
75 | To verify this, you can:
76 |
77 | * Try the request from a host on the subnet. It will succeed.
78 | * Try the request from a host with a properly configured active VPN running. It will STILL succeed.
79 | * Try the request from a mobile phone with wifi off. u'll get a 401 Unauthorized response!
80 | * Try whatever else you can think of to trick traefik. It won't work :)
81 |
82 | > **NOTE**: If you run traefik behind another proxy that uses X-Forwarded-For header, you may have to configure other settings to [pick the right IP](https://docs.traefik.io/middlewares/ipwhitelist/#configuration-options)
83 |
--------------------------------------------------------------------------------
/docs/pihole-dnsmasq.md:
--------------------------------------------------------------------------------
1 | # Configuring Pi-Hole properly with dnsmasq
2 |
3 | This documentation is expanded from [the pihole discourse docs](https://discourse.pi-hole.net/t/how-do-i-configure-my-devices-to-use-pi-hole-as-their-dns-server/245). That document is great, but if you aren't already familiar with [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html) it may be confusing.
4 |
5 | IMO, this is the **most proper** method of configuring a pihole.
6 |
7 | ## Goals
8 |
9 | * to automatically configure DHCP clients to use a local pihole for DNS
10 | * to use our router as a dnsmasq-based dhcp server (instead of using pihole for dhcp)
11 | * to preserve hostname routing for hosts on your LAN, so you don't ever have to touch `/etc/hosts`
12 | * to allow pihole logs to show queries based on hostname rather than IP.
13 |
14 | ## dnsmasq
15 |
16 | dnsmasq is both a DNS *and* a DHCP server. It does both, and we're going to use it for both. What matters is the ordering.
17 |
18 | DHCP servers handle assigning IP addresses to hosts on network. Since dnsmasq is both a DHCP server and a DNS resolver, it can remember what host it assigned what IP, so when you query for `myhost.lan`, dnsmasq will notice that it has a local record for `myhost` and return its local IP.
19 |
20 | Here's how we want the query order to work:
21 |
22 | ``` plain
23 | +------------------------------+
24 | | |
25 | | Internet (1.1.1.1) |
26 | | |
27 | +-------------+----------------+
28 | ^
29 | |
30 | +-------------+----------------+
31 | | |
32 | | Router (192.168.1.1) |
33 | | |
34 | +-------------+----------------+
35 | ^
36 | |
37 | |
38 | +-------------+----------------+
39 | | |
40 | | Pihole (192.168.1.33) |
41 | | |
42 | +------------------------------+
43 | ```
44 |
45 | ### Setting dnsmasq options
46 |
47 | We need to configure the router to tell DHCP clients that the local DNS server is pihole, at `192.168.1.33` (for example). This happens when a client leases an IP, so after you change these settings, you may need to use `dhclient` to refresh your lease.
48 |
49 | I have a Ubiquiti Edgerouter X, so [enabling dnsmasq](https://help.ui.com/hc/en-us/articles/115002673188-EdgeRouter-DHCP-Server-Using-Dnsmasq) is easy enough.
50 |
51 | * Change dnsmasq's DNS forwarding to the public server you choose. I like `1.1.1.2` from [cloudflare](https://blog.cloudflare.com/introducing-1-1-1-1-for-families/)
52 | * Set dnsmasq `dhcp-option` option 6 `dns-server` to the IP of your Pi Hole. `dhcp-option=6,192.168.1.33` is the likely syntax
53 | * Set the system nameserver to be localhost, so all local DNS queries also go through dnsmasq.
54 | * Set a local domain name, like `lan` so that all your hostnames will be accessible as `hostname.lan`.
55 |
56 | 
57 |
58 | 
59 |
60 | My edgerouter config looks like this
61 |
62 | ``` conf
63 | service {
64 | dhcp-server {
65 | disabled false
66 | shared-network-name LAN {
67 | authoritative enable
68 | subnet 192.168.1.0/24 {
69 | default-router 192.168.1.1
70 | domain-name lan
71 | lease 86400
72 | start 192.168.1.38 {
73 | stop 192.168.1.243
74 | }
75 | }
76 | }
77 | static-arp disable
78 | use-dnsmasq enable
79 | }
80 | dns {
81 | forwarding {
82 | cache-size 150
83 | listen-on switch0
84 | name-server 1.1.1.2
85 | name-server 1.0.0.2
86 | options dhcp-option=6,192.168.1.33
87 | }
88 | }
89 | }
90 | system {
91 | name-server 127.0.0.1
92 | }
93 | ```
94 |
95 | ## Pi Hole Config
96 |
97 | Pihole will direct all un-blocked DNS to your router, the upstream dns server.
98 |
99 | * Set your router IP as the only custom Upstream DNS Server
100 | * Use DNSSEC
101 | * Enable conditional forwarding and specify your local domain, `lan`
102 |
103 | 
104 |
105 | 
106 |
107 | ## Verifying it worked
108 |
109 | https://askubuntu.com/questions/152593/command-line-to-list-dns-servers-used-by-my-system
110 |
111 | ```bash
112 | # List interfaces
113 | nmcli dev
114 | # Show details
115 | nmcli dev show eth0
116 | # Look for "IP4.DNS", it should be your PiHole IP
117 | ```
118 |
119 | In addition, try renewing a DHCP lease on a client to ensure the new lease is sending your PiHole's IP for the DNS server. If it's not working, check to make sure that the DHCP Server in `Services` has blank entries for both `DNS Server 1` and `DNS Server 2`.
120 |
121 | 
122 |
123 | ## DNS over TLS with Unbound
124 |
125 | I followed [this guide from chameth.com](https://chameth.com/dns-over-tls-on-edgerouter-lite/). Unlike Chris, I didn't want to _replace_ dnsmasq with unbound because I still wanted DHCP and automatic hostname resolution features that somehow only dnsmasq seems to provide.
126 |
127 | Instead, I altered dnsmasq to unbind from all interfaces so that I could run dnsmasq only on `switch0` at 192.168.1.1 and 127.0.0.1.
128 |
129 | Now, I run unbound on 127.0.0.2 and forward queries that dnsmasq is unable to resolve on to unbound. Here's my amended config.
130 |
131 | ``` conf
132 | service{
133 | dns {
134 | forwarding {
135 | cache-size 150
136 | listen-on switch0
137 | name-server 127.0.0.2
138 | options dhcp-option=6,192.168.1.175,192.168.1.112
139 | options bind-interfaces
140 | options listen-address=127.0.0.1
141 | options listen-address=192.168.1.1
142 | }
143 | }
144 | }
145 | ```
146 |
147 | > About the only time when this is useful is when running another nameserver (or another instance of dnsmasq) on the same machine. Setting this option also enables multiple instances of dnsmasq which provide DHCP service to run in the same machine.
148 |
149 | My unbound config can be found in [/etc/unbound.conf](/etc/unbound.conf)
150 |
151 | ## Redirect hard-coded DNS device queries to PiHole
152 |
153 | The idea is to capture queries coming from stubborn IoT devices like smart speakers and smart TVs. I cobbled this together from [a community thread](https://community.ui.com/questions/Intercepting-and-Re-Directing-DNS-Queries/cd0a248d-ca54-4d16-84c6-a5ade3dc3272)
154 |
155 | ``` conf
156 | service {
157 | nat {
158 | rule 1002 {
159 | description "Redirect DNS"
160 | destination {
161 | port 53
162 | }
163 | inbound-interface switch0
164 | inside-address {
165 | address 192.168.1.175
166 | port 53
167 | }
168 | log disable
169 | protocol tcp_udp
170 | source {
171 | group {
172 | address-group !DNS-Servers
173 | }
174 | }
175 | type destination
176 | }
177 | rule 5002 {
178 | description "Translate DNS to Internal reply"
179 | destination {
180 | group {
181 | address-group DNS-Servers
182 | }
183 | port 53
184 | }
185 | log disable
186 | outbound-interface switch0
187 | protocol tcp_udp
188 | type masquerade
189 | }
190 | ...
191 | }
192 | }
193 | ```
194 |
195 | Note the address group. You'll have to create a firewall address group:
196 |
197 | ``` conf
198 | firewall {
199 | group {
200 | address-group DNS-Servers {
201 | address 192.168.1.175
202 | address 192.168.1.112
203 | description "Internal DNS servers"
204 | }
205 | }
206 | }
207 | ```
208 |
209 | ## Adguard Home
210 |
211 | You can specify your router's IP for LAN reverse resolution inside adguard.
212 |
--------------------------------------------------------------------------------
/docs/ubuntu-expand-lvm.md:
--------------------------------------------------------------------------------
1 | # Expand LVM to fill disk on ubuntu
2 |
3 | Occasionally I end up with an LVM that doesn't fill the physical disk. [This blog contains the solution](http://ryandoyle.net/posts/expanding-a-lvm-partition-to-fill-remaining-drive-space/)
4 |
5 | In summary:
6 |
7 | ``` bash
8 | df -h
9 | sudo lvresize -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
10 | sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
11 | ```
12 |
--------------------------------------------------------------------------------
/docs/wildcard-certs.md:
--------------------------------------------------------------------------------
1 | # ACME (Lets Encrypt) Wildcard SSL in Traefik
2 |
3 | > **GOAL:** In this document, you'll learn how to set up Wildcard SSL with ACME in Traefik and create a service with HTTP to HTTPS forced redirect. In all my docs, the end result is a *working example* rather than bullet points and hand-waving.
4 |
5 | Like much of the Traefik v2 documentation, the [Official ACME Docs](https://docs.traefik.io/https/acme/) aren't very good. Hopefully these snippets help.
6 |
7 | ## Getting Started
8 |
9 | Wildcard SSL is great because it lets you manage fewer certificates and spin up new services and new subdomains without dealing with ACME every time. If you're like me and find ACME (or your own HTTP infrastructure) a little unreliable, then having to interact with it less is a huge positive.
10 |
11 | First, Wildcard SSL can only be done using [DNS Challenge Authentication](https://docs.traefik.io/https/acme/#dnschallenge). To get an SSL certificate for any subdomain, you need to prove ownership of the entire DNS configuration as opposed to any single webserver.
12 |
13 | ### DNS Config
14 |
15 | Because you're doing DNS Auth, you actually don't *need* a dns record to get the certificate, but you do need one to *test* it.
16 |
17 | 1. Create a DNS A record to point to your webserver for `whoami.mydoain.com`
18 | 1. Verify that your router or firewall is configured to allow 80 AND 443 through the firewall to your webserver.
19 |
20 | ### Cloudflare API token
21 |
22 | > **NOTE** This example uses cloudflare as the [DNS provider](https://docs.traefik.io/https/acme/#providers), you may need to replace my env with your own.
23 |
24 | To verify your domain, letsencrypt will use your DNS provider's API to create a `TXT` record with the key `_acme-challenge`. Then, the ACME auth service will query your DNS settings to see if the record is there, proving you own the domain.
25 |
26 | 1. Create an API token by going to your profile then clicking the API token tab. Permissions should look something like this. I use the same token for `DNS` and `ZONE`, and I think it's probably easiest for you to do the same.
27 | 1. If verification fails, look at the traefik logs. **You may need to manually delete the `TXT` record in your DNS console before trying again.**
28 |
29 | 
30 |
31 | ### Running Traefik
32 |
33 | ```bash
34 | export LETSENCRYPT_EMAIL=youremail@domain.com
35 | export CF_API_EMAIL=youremail@domain.com
36 | export CF_TOKEN=your_token
37 | docker run --rm --name traefik \
38 | --env CF_API_EMAIL=${CF_API_EMAIL} \
39 | --env CF_DNS_API_TOKEN=${CF_TOKEN} \
40 | --env CF_ZONE_API_TOKEN=${CF_TOKEN} \
41 | --volume /var/run/docker.sock:/var/run/docker.sock \
42 | --volume "/var/lib/traefik/letsencrypt:/letsencrypt" \
43 | --publis 8080:8080 \
44 | --publish 80:80 \
45 | --publish 443:443 \
46 | traefik:v2.2 \
47 | --api.insecure=true \
48 | --providers.docker=true \
49 | --providers.docker.exposedByDefault=false \
50 | --entrypoints.web.address=:80 \
51 | --entrypoints.websecure.address=:443 \
52 | --certificatesResolvers.myresolver.acme.caServer="https://acme-v02.api.letsencrypt.org/directory" \
53 | --certificatesresolvers.myresolver.acme.dnschallenge=true \
54 | --certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare \
55 | --certificatesresolvers.myresolver.acme.email="${LETSENCRYPT_EMAIL}" \
56 | --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
57 | ```
58 |
59 | Let's break down some of these lines. Anything not mentioned below is unrelated to SSL, but leaving it out may break my examples.
60 |
61 | ``` bash
62 | # This provides your API information to traefik through environment variables
63 | --env CF_API_EMAIL=${CF_API_EMAIL}
64 | --env CF_DNS_API_TOKEN=${CF_TOKEN}
65 | --env CF_ZONE_API_TOKEN=${CF_TOKEN}
66 | # Your certificates will be written to a JSON file that need to be stored persistently on your host disk.
67 | --volume "/var/lib/traefik/letsencrypt:/letsencrypt"
68 | # Use both entrypoints, because you'll need to accept connections on 80 to redirect to 443
69 | --entrypoints.web.address=:80 \
70 | --entrypoints.websecure.address=:443 \
71 | # Create a resolver named "myresolver" pointed at the production ACME api
72 | --certificatesResolvers.myresolver.acme.caServer="https://acme-v02.api.letsencrypt.org/directory" \
73 | --certificatesresolvers.myresolver.acme.dnschallenge=true \
74 | --certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare \
75 | --certificatesresolvers.myresolver.acme.email="${LETSENCRYPT_EMAIL}" \
76 | --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
77 | ```
78 |
79 | At this point, **traefik will not generate a cert** because you don't have any services using the `myresolver` resolver yet.
80 |
81 | ### Running a service
82 |
83 | ```bash
84 | export MY_APP_DOMAIN=mydomain.com
85 | docker run --rm --name whoami \
86 | --label traefik.enable=true \
87 | --label traefik.http.services.my-service.loadbalancer.server.port="80" \
88 | --label traefik.http.middlewares.my-insecure-redirect-middleware.redirectscheme.scheme="https" \
89 | --label traefik.http.routers.my-route.entrypoints=web \
90 | --label traefik.http.routers.my-route.rule="Host(`whoami.${MY_APP_DOMAIN}`)" \
91 | --label traefik.http.routers.my-route.middlewares="my-insecure-redirect-middleware" \
92 | --label traefik.http.routers.my-secure-route.entrypoints=websecure \
93 | --label traefik.http.routers.my-secure-route.rule="Host(`whoami.${MY_APP_DOMAIN}`)" \
94 | --label traefik.http.routers.my-secure-route.tls.domains[0].main="*.${DNS_DOMAIN}" \
95 | --label traefik.http.routers.my-secure-route.tls.certresolver="myresolver" \
96 | --label traefik.http.routers.my-secure-route.tls=true \
97 | containous/whoami
98 | ```
99 |
100 | Let's break this config down.
101 |
102 | ``` bash
103 | # Create an HTTP (insecure) route that only has redirect middleware
104 | --label traefik.http.services.my-service.loadbalancer.server.port="80" \
105 | --label traefik.http.middlewares.my-insecure-redirect-middleware.redirectscheme.scheme="https" \
106 | # This insecure route only listens on the insecure entrypoint
107 | --label traefik.http.routers.my-route.entrypoints=web \
108 | # Both secure and insecure need the routing rule
109 | --label traefik.http.routers.my-route.rule="Host(`whoami.${MY_APP_DOMAIN}`)" \
110 | # Very important to register the middleware on the route
111 | --label traefik.http.routers.my-route.middlewares="my-insecure-redirect-middleware" \
112 | # Create an HTTPS (secure) route that references "myresolver" from traefik config
113 | --label traefik.http.routers.my-secure-route.entrypoints=websecure \
114 | --label traefik.http.routers.my-secure-route.rule="Host(`whoami.${MY_APP_DOMAIN}`)" \
115 | # Use your wildcard and NOT the actual domain for domains[0].main.
116 | --label traefik.http.routers.my-secure-route.tls.domains[0].main="*.${MY_APP_DOMAIN}" \
117 | # If you ARENT hosting any routes for the bare domain (mydomain.com), you can REMOVE the SAN (Subject Alternative Name)
118 | # However, leaving it here doesn't hurt anything!
119 | --label traefik.http.routers.my-secure-route.tls.domains[0].san="${MY_APP_DOMAIN}" \
120 | --label traefik.http.routers.my-secure-route.tls.certresolver="myresolver" \
121 | --label traefik.http.routers.my-secure-route.tls=true \
122 | ```
123 |
124 | Why is https redirect so complicated? [Nobody seems to know](https://github.com/containous/traefik/issues/4863).
125 |
126 | ## Summary
127 |
128 | You should now be able to access https://whoami.mydomain.com in your browser!
129 |
--------------------------------------------------------------------------------
/docs/wireguard-question.md:
--------------------------------------------------------------------------------
1 | # Sharing WireGuard network with other containers via docker
2 |
3 | I'm trying to do something a little "backwards". Most WireGuard tutorials show you how to share a WireGuard container's internet connection with peers, forwarding traffic from `wg0` to `eth0`. For example, the VPN Server configuration [described on ArchWiki](https://wiki.archlinux.org/index.php/WireGuard)
4 |
5 | **I want to do the opposite**. I want containers that share a network with WireGuard to be able to access its peers.
6 |
7 | ### wg0.conf
8 |
9 | My current config is lifted straight from archwiki, and works in the traditional VPN server way.
10 |
11 | ``` conf
12 | [Interface]
13 | Address = 10.200.200.1/24
14 | ListenPort = 51820
15 | PrivateKey = SERVER_PRIVATE_KEY
16 | PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
17 | PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
18 |
19 | [Peer]
20 | # foo
21 | PublicKey = PEER_FOO_PUBLIC_KEY
22 | PresharedKey = PRE-SHARED_KEY
23 | AllowedIPs = 10.200.200.2/32
24 |
25 | [Peer]
26 | # bar
27 | PublicKey = PEER_BAR_PUBLIC_KEY
28 | PresharedKey = PRE-SHARED_KEY
29 | AllowedIPs = 10.200.200.3/32
30 | ```
31 |
32 | ### docker-compose.yml
33 |
34 | ``` yml
35 | version: '3.8'
36 | services:
37 | wireguard:
38 | image: linuxserver/wireguard
39 | container_name: wireguard
40 | cap_add:
41 | - NET_ADMIN
42 | - SYS_MODULE
43 | environment:
44 | - PUID=1000
45 | - PGID=1000
46 | - TZ=${TIME_ZONE}
47 | - SERVERURL="wireguard.${DNS_DOMAIN}"
48 | - SERVERPORT=51820
49 | - PEERS=2
50 | - PEERDNS=192.168.1.10 # DNS on my LAN, not important.
51 | - INTERNAL_SUBNET=10.200.200.0
52 | ports:
53 | - "51820:51820/udp"
54 | volumes:
55 | - wireguardconfig:/config
56 | - /lib/modules:/lib/modules
57 | sysctls:
58 | - "net.ipv4.ip_forward=1"
59 | - "net.ipv4.conf.all.src_valid_mark=1"
60 | networks:
61 | - wireguard-net
62 |
63 | ubuntu:
64 | image: ubuntu
65 | container_name: ubuntu
66 | entrypoint: /bin/sleep
67 | command: 10000000
68 | networks:
69 | - wireguard-net
70 |
71 | volumes:
72 | wireguardconfig:
73 |
74 | wireguard-net:
75 | name: wireguard-net
76 | ipam:
77 | config:
78 | - subnet: 10.200.0.0/16
79 | ```
80 |
81 | ## Goal
82 |
83 | I want to be able to `docker exec` into ubunut and run `ping 10.200.200.2` when peer 2 is active. I don't think I'm far off. What am I missing?
--------------------------------------------------------------------------------
/etc/authconfig.ini:
--------------------------------------------------------------------------------
1 | # Configuration example
2 | rule.webtop.action = auth
3 | rule.webtop.rule = Host(`webtop.subdavis.com`)
4 | rule.webtop.whitelist = davis.a.brandon@gmail.com
5 |
--------------------------------------------------------------------------------
/etc/clickhouse/clickhouse-config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | warning
4 | true
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/etc/clickhouse/clickhouse-user-config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 0
5 | 0
6 |
7 |
8 |
--------------------------------------------------------------------------------
/etc/mdadm.conf:
--------------------------------------------------------------------------------
1 | ARRAY /dev/md/0 metadata=1.2 name=localhost:0 UUID=86102d98:fe537e8c:a1bb0f9e:88341045
2 |
--------------------------------------------------------------------------------
/etc/sshd_config:
--------------------------------------------------------------------------------
1 | ChallengeResponseAuthentication no
2 | UsePAM no
3 | X11Forwarding no
4 | PasswordAuthentication no
5 | PrintMotd yes
6 | AcceptEnv LANG LC_*
7 | Subsystem sftp /usr/lib/openssh/sftp-server
8 | Match User edgerouter-backup
9 | PasswordAuthentication yes
10 |
--------------------------------------------------------------------------------
/etc/traefik-logrotate.conf:
--------------------------------------------------------------------------------
1 | /media/local/traefik/logs/*.log {
2 | daily
3 | rotate 30
4 | missingok
5 | notifempty
6 | compress
7 | dateext
8 | dateformat .%Y-%m-%d
9 | postrotate
10 | "$HOME/bin/docker" -H unix:///run/user/1000/docker.sock kill --signal="USR1" $($HOME/bin/docker ps | grep traefik | awk '{print $1}')
11 | endscript
12 | }
13 |
--------------------------------------------------------------------------------
/etc/traefik/rules-fail2ban.yml:
--------------------------------------------------------------------------------
1 | http:
2 | middlewares:
3 | fail2ban-mddl:
4 | plugin:
5 | fail2ban:
6 | whitelist:
7 | ip:
8 | - "192.168.1.1"
9 | rules:
10 | bantime: "12h"
11 | findtime: "10m"
12 | maxretry: 2000
13 | enabled: true
14 | urlregexp: ""
15 | ports: "80:443"
16 |
--------------------------------------------------------------------------------
/etc/unbound.conf:
--------------------------------------------------------------------------------
1 |
2 | server:
3 | verbosity: 1
4 | interface: 127.0.0.2
5 | port: 53
6 | do-ip4: yes
7 | do-ip6: no
8 | do-udp: yes
9 | do-tcp: yes
10 |
11 | access-control: 127.0.0.0/8 allow
12 |
13 | hide-identity: yes
14 | hide-version: yes
15 | harden-glue: yes
16 | harden-dnssec-stripped: yes
17 |
18 | cache-min-ttl: 900
19 | cache-max-ttl: 14400
20 | prefetch: yes
21 | rrset-roundrobin: yes
22 | ssl-upstream: yes
23 | use-caps-for-id: yes
24 |
25 | # These are addresses on your private network,
26 | # and are not allowed to be returned for public
27 | # internet names.
28 | private-address: 192.168.0.0/16
29 | private-address: 172.16.0.0/12
30 | private-address: 10.0.0.0/8
31 |
32 | logfile: "/var/lib/unbound/unbound.log"
33 | verbosity: 0
34 | val-log-level: 3
35 |
36 | forward-zone:
37 | name: "."
38 | forward-addr: 1.1.1.1@853
39 | forward-addr: 1.0.0.1@853
40 |
--------------------------------------------------------------------------------
/logrotate.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Rotate log files
3 | Documentation=man:logrotate(8) man:logrotate.conf(5)
4 | ConditionACPower=true
5 |
6 | [Service]
7 | Type=oneshot
8 | ExecStart=/usr/sbin/logrotate -s %h/logrotate.state %h/selfhosted/etc/traefik-logrotate.conf
9 |
10 | # performance options
11 | #Nice=19
12 | #IOSchedulingClass=best-effort
13 | #IOSchedulingPriority=7
14 |
15 | # hardening options
16 | # details: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
17 | # no ProtectHome for userdir logs
18 | # no PrivateNetwork for mail deliviery
19 | # no ProtectKernelTunables for working SELinux with systemd older than 235
20 | # no MemoryDenyWriteExecute for gzip on i686
21 | #PrivateDevices=true
22 | #PrivateTmp=true
23 | #ProtectControlGroups=true
24 | #ProtectKernelModules=true
25 | #ProtectSystem=full
26 | #RestrictRealtime=true
27 |
--------------------------------------------------------------------------------
/logrotate.timer:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Daily rotation of log files
3 | Documentation=man:logrotate(8) man:logrotate.conf(5)
4 |
5 | [Timer]
6 | OnCalendar=daily
7 | AccuracySec=12h
8 | Persistent=true
9 |
10 | [Install]
11 | WantedBy=timers.target
12 |
--------------------------------------------------------------------------------
/media-4tb.mount:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=4TB Disk
3 |
4 | [Mount]
5 | What=UUID=98d3794b-3f70-4bbc-8d39-ffd0847c621b
6 | Where=/media/4tb
7 | Type=ext4
8 | Options=defaults,auto,noatime,exec
9 |
10 | [Install]
11 | WantedBy=multi-user.target
12 |
--------------------------------------------------------------------------------
/media-primary.mount:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=RAID1
3 |
4 | [Mount]
5 | What=/dev/md0
6 | Where=/media/primary
7 | Type=ext4
8 | Options=defaults,auto,noatime,exec
9 |
10 | [Install]
11 | WantedBy=multi-user.target
12 |
--------------------------------------------------------------------------------
/media-secondary.mount:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=General data
3 |
4 | [Mount]
5 | What=UUID=7de2e82b-e282-468f-976a-8a2e6c9722a8
6 | Where=/media/secondary
7 | Type=ext4
8 | Options=defaults,auto,noatime,exec
9 |
10 | [Install]
11 | WantedBy=multi-user.target
12 |
--------------------------------------------------------------------------------
/media-wildy.mount:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Wildy
3 |
4 | [Mount]
5 | What=/dev/sdb1
6 | Where=/media/wildy
7 | #Type=ext2
8 | Options=defaults,auto,noatime,exec
9 |
10 | [Install]
11 | WantedBy=multi-user.target
12 |
--------------------------------------------------------------------------------
/wildy.compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 | services:
3 |
4 | transmission:
5 | image: haugene/transmission-openvpn:latest
6 | container_name: transmission
7 | restart: always
8 | dns: 1.1.1.1
9 | user: ${XID:-1000}
10 | cap_add:
11 | - NET_ADMIN
12 | devices:
13 | - /dev/net/tun
14 | volumes:
15 | - ${PRIMARY_MOUNT}/data:/data
16 | - /etc/localtime:/etc/localtime:ro
17 | environment:
18 | - CREATE_TUN_DEVICE=true
19 | - "OPENVPN_PROVIDER=${OPENVPN_PROVIDER}"
20 | - "OPENVPN_USERNAME=${TORRENT_USERNAME}"
21 | - "OPENVPN_PASSWORD=${TORRENT_PASSWORD}"
22 | - "TRANSMISSION_UMASK=0"
23 | - "OPENVPN_OPTS=--inactive 3600 --ping 10 --ping-exit 60"
24 | - LOCAL_NETWORK=${LOCAL_NETWORK}
25 |
26 | transmission_proxy:
27 | image: haugene/transmission-openvpn-proxy:latest
28 | container_name: transmission_proxy
29 | restart: always
30 | ports:
31 | - "9091:8080"
32 | depends_on:
33 | - transmission
34 |
35 | watchtower:
36 | image: containrrr/watchtower:latest
37 | container_name: watchtower
38 | restart: always
39 | command: --schedule "0 0 */6 * * *"
40 | volumes:
41 | - ${SOCK_PATH}:/var/run/docker.sock
42 | - "${PRIMARY_MOUNT}/watchtower/config/:/config"
43 | - "${PRIMARY_MOUNT}/watchtower/docker-config.json:/config.json"
44 |
--------------------------------------------------------------------------------