Files
docker-infrastructure/tailscale-proxy/docker-compose.yaml
T
pipistrello ea9501f719 tailscale-proxy: add the missing IKEv2 VPN front, and correct the stale exit node
This file has been unsafe to deploy for two separate reasons. Both are fixed here,
and the result is now byte-identical in effective config to what actually runs on
the client-02 host (verified by diffing `docker compose config` on both).

1. ts-vpn + ikev2 were missing entirely.

The IKEv2 VPN front has run on the host since 2026-09-05, but was never mirrored
here — this file defined only ts-proxy, proxy and telemt. Deploying it as-is would
have taken the VPN down for the Windows and iOS clients. Added both services, plus
ikev2-egress.sh, which the ikev2 service mounts and cannot start without.

ts-vpn is a SECOND Tailscale node in kernel/TUN mode (unlike ts-proxy, which is
userspace): it forwards raw IP packets rather than TCP streams, so a userspace
SOCKS5 is useless for it. It owns the network namespace that ikev2 joins via
network_mode, which is also why the ports and sysctls live on ts-vpn and not on
ikev2 — Docker forbids net.* sysctls on a network_mode:service container.

2. ts-proxy still pointed at the pre-migration exit node.

  TS_EXTRA_ARGS  --exit-node=100.121.234.85  ->  --exit-node=100.106.197.99
  TS_HOSTNAME / hostname  technohim-lan-proxy -> 02-tekhnohim-lan-proxy

100.121.234.85 has not existed since the 2026-08-27 tailnet migration. This is
worse than a dead setting: with the exit node absent this stack does NOT fail
closed — it silently egresses through the local ISP, so a redeploy from this repo
would have turned a censorship-avoiding proxy into a plain local-egress one while
still reporting healthy. This drift was recorded as an open issue in the migration
write-up on 2026-08-27; these are the exact substitutions it prescribed.

Not included, deliberately: ts-vpn.env and ikev2.env, which hold TS_AUTHKEY and the
VPN credentials. Like every other config in this stack they are staged on the host
by hand — a git push does not deploy config here, and these must never enter git.
Both extracted files were scanned for secrets before being added.
2026-09-09 23:06:04 +03:00

146 lines
6.5 KiB
YAML

services:
ts-proxy:
image: tailscale/tailscale:v1.98.9
container_name: ts-proxy
hostname: 02-tekhnohim-lan-proxy
restart: unless-stopped
env_file: ["/mnt/containers/tailscale-proxy/ts.env"]
environment:
TS_HOSTNAME: 02-tekhnohim-lan-proxy
TS_STATE_DIR: /var/lib/tailscale
TS_USERSPACE: "true"
TS_ACCEPT_DNS: "false"
TS_EXTRA_ARGS: "--exit-node=100.106.197.99 --reset"
TS_TAILSCALED_EXTRA_ARGS: "--socks5-server=0.0.0.0:1055 --outbound-http-proxy-listen=0.0.0.0:1056"
volumes: ["/mnt/containers/tailscale-proxy/state:/var/lib/tailscale:z"]
networks: [proxynet]
# 3proxy HTTP front (:3128), LAN passwordless and external password-
# authenticated, egressing via the Tailscale exit node. (The generic SOCKS5
# :1080 was retired 2026-07-24 — see below.)
proxy:
image: ghcr.io/3proxy/3proxy:latest
container_name: ts-lan-proxy
restart: unless-stopped
# Same Docker default-1024 trap that took Telemt down on 2026-09-09; 3proxy
# does not raise its own soft limit either. Steady state is ~22 descriptors.
ulimits:
nofile:
soft: 65536
hard: 524288
depends_on: [ts-proxy]
volumes:
- "/mnt/containers/tailscale-proxy/3proxy.cfg:/etc/3proxy/3proxy.cfg:ro,z"
- "/mnt/containers/tailscale-proxy/3proxy.secret:/etc/3proxy/3proxy.secret:ro,z"
ports:
- "192.168.0.35:3128:3128"
logging:
driver: syslog
options:
syslog-address: "udp://192.168.0.35:514"
syslog-format: rfc3164
tag: tailscale-proxy
networks: [proxynet]
# Telegram Fake-TLS MTProto proxy on :1080. Uses Telemt (the same proven
# implementation as the Hetzner endpoint) after alexbers/mtprotoproxy's
# Fake-TLS handshake was rejected by real clients (see the mtproto diagnostic).
# Only DC-bound traffic is chained out through the Tailscale userspace SOCKS5
# ([[upstreams]] socks5 ts-proxy:1055 in telemt.toml), so egress rides the
# Hetzner exit node; the client-facing hop is direct Fake-TLS.
# telemt.toml is host-only (0600, uid 65532/nonroot, NOT in git) - holds the secret.
telemt:
image: ghcr.io/telemt/telemt:3.4.25
container_name: ts-lan-telemt
restart: unless-stopped
# Docker default soft nofile is 1024. A connection flood on the public :1080
# exhausted it on 2026-09-09 and accept() then failed for EVERY caller, for
# ~8 minutes. Steady state needs only ~45-73 descriptors.
ulimits:
nofile:
soft: 65536
hard: 524288
depends_on: [ts-proxy]
environment:
NO_COLOR: "1" # cleaner `docker logs` (no ANSI escapes)
volumes:
- "/mnt/containers/tailscale-proxy/telemt/telemt.toml:/app/config.toml:ro,z"
ports:
- "192.168.0.35:1080:1080"
# Host :9090 belongs to Cockpit; expose Telemt metrics on :9092 instead.
- "192.168.0.35:9092:9090"
# Stays on json-file: telemt's tracing logs don't ship cleanly over the
# syslog driver, and `docker logs ts-lan-telemt` is worth keeping for this
# critical service. Prometheus metrics are scraped by Zabbix on :9092.
networks: [proxynet]
# ---------------------------------------------------------------------------
# IKEv2 VPN front for iOS/macOS/Windows/Android (added 2026-09-05).
#
# ts-vpn is a SECOND Tailscale exit-node client, in KERNEL (TUN) mode - unlike
# ts-proxy it must forward raw IP packets, not TCP streams, so a userspace
# SOCKS5 is useless here. It owns the network namespace that the IKEv2 server
# (ikev2) joins via network_mode. Inside that namespace Tailscale's policy
# routing sends the default route into the tunnel (route table 52), so VPN
# clients egress from the Hetzner exit node.
# --netfilter-mode=off : Tailscale installs NO nft chains here; NAT + MSS
# clamp for the VPN subnet are done by ikev2-egress.sh (same netns).
# sysctls live here (Docker forbids net.* sysctls on a network_mode:service
# container) - hwdsl2 wants ip_forward=1 and rp_filter/redirects off.
# Ports are published HERE (the namespace owner), LAN-bound; the router
# already forwards WAN udp/500 + udp/4500 to 192.168.0.35.
# If ts-vpn is ever restarted alone, restart ikev2 too - a network_mode:service
# container keeps a stale namespace otherwise (`docker compose up -d`).
ts-vpn:
image: tailscale/tailscale:v1.98.9
container_name: ts-vpn
hostname: 02-tekhnohim-lan-vpn
restart: unless-stopped
env_file: ["/mnt/containers/tailscale-proxy/ts-vpn.env"] # TS_AUTHKEY=... 0600, NOT in git
environment:
TS_HOSTNAME: 02-tekhnohim-lan-vpn
TS_STATE_DIR: /var/lib/tailscale
TS_USERSPACE: "false"
TS_ACCEPT_DNS: "false"
TS_EXTRA_ARGS: "--exit-node=100.106.197.99 --netfilter-mode=off --reset"
cap_add: [NET_ADMIN]
devices: ["/dev/net/tun:/dev/net/tun"]
sysctls:
net.ipv4.ip_forward: "1"
net.ipv4.conf.all.rp_filter: "0"
net.ipv4.conf.default.rp_filter: "0"
net.ipv4.conf.all.send_redirects: "0"
net.ipv4.conf.default.send_redirects: "0"
net.ipv4.conf.all.accept_redirects: "0"
net.ipv4.conf.default.accept_redirects: "0"
volumes: ["/mnt/containers/tailscale-proxy/ts-vpn-state:/var/lib/tailscale:z"]
ports:
- "192.168.0.35:500:500/udp"
- "192.168.0.35:4500:4500/udp"
networks: [proxynet]
# hwdsl2 IPsec server in IKEv2-only mode (no PSK/L2TP/XAuth exposure). It
# generates ready-to-import client profiles: .mobileconfig (iOS/macOS),
# .p12 (Windows/Linux), .sswan (Android). Certificates/clients persist in the
# ikev2/ volume. ikev2-egress.sh runs alongside run.sh and keeps
# - VPN subnet -> tailscale0 MASQUERADE + MSS clamp (exit-node egress),
# - IKE/ESP replies on the docker bridge (ip rules ahead of Tailscale's),
# - the office LAN reachable from the VPN (route via the bridge gateway).
ikev2:
image: hwdsl2/ipsec-vpn-server@sha256:2e939ffe5913c9a34a3f5b72d75a97e9a2f16824d223bf042b88a567ffeabe9b # :latest of 2026-08-28
container_name: ts-lan-ikev2
restart: unless-stopped
depends_on: [ts-vpn]
network_mode: "service:ts-vpn"
env_file: ["/mnt/containers/tailscale-proxy/ikev2.env"]
cap_add: [NET_ADMIN]
volumes:
- "/mnt/containers/tailscale-proxy/ikev2:/etc/ipsec.d:z"
- "/lib/modules:/lib/modules:ro"
- "/mnt/containers/tailscale-proxy/ikev2-egress.sh:/usr/local/bin/ikev2-egress.sh:ro,z"
entrypoint: ["/bin/sh", "-c", "/usr/local/bin/ikev2-egress.sh & exec /opt/src/run.sh"]
networks:
proxynet:
name: tailscale-proxynet