mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:47:02 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Every script under /usr/local/bin must be a repo file. install_cert_sync wrote /usr/local/bin/sync-dot-cert.sh from a heredoc with the domain, Caddy container and cert dir baked in. The script now lives at linux_configuration/scripts/sync-dot-cert.sh, takes those three as arguments (defaults = the installer's constants), and is installed with install -m 755. dot-cert-sync.service and the first sync pass the arguments, so --domain reaches the timer too. Test asserts the installed file is the tracked one byte for byte and runs the script itself against a docker shim (reload only on change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yhTkYV6ZeuAbQYvykFGFc Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
159 lines
5.3 KiB
Bash
Executable File
159 lines
5.3 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# Expose the existing dnsmasq blocklist over DNS-over-TLS (port 853).
|
|
#
|
|
# Why this exists:
|
|
# setup_dns_blocker.sh serves the blocklist over plain UDP/53, which only
|
|
# works on the LAN -- and Android's default "Automatic" Private DNS uses DoH
|
|
# and bypasses it entirely. A phone off the LAN gets no filtering at all.
|
|
# Terminating DoT here lets the phone pin Private DNS to a resolver that
|
|
# applies the blocklist, anywhere, with no root and no VPN slot consumed.
|
|
#
|
|
# Why stunnel rather than Caddy:
|
|
# DoT is a raw TLS stream, not HTTPS. Terminating it in Caddy needs the
|
|
# `layer4` module, which the stock `caddy:2.8` image does not ship -- that
|
|
# would mean an xcaddy rebuild of a container currently serving five live
|
|
# sites. stunnel does exactly this one job, and it can reuse the Let's
|
|
# Encrypt certificate Caddy already renews, so there is no second ACME
|
|
# client competing for port 80.
|
|
#
|
|
# Usage:
|
|
# ./setup_dot_resolver.sh [--domain <fqdn>] [--check]
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
readonly DEFAULT_DOMAIN="dns.kuhy.duckdns.org"
|
|
readonly CADDY_CONTAINER="gitea-caddy"
|
|
readonly CERT_DIR="/etc/stunnel/dot"
|
|
readonly STUNNEL_CONF="/etc/stunnel/dot-resolver.conf"
|
|
readonly SYNC_SCRIPT="/usr/local/bin/sync-dot-cert.sh"
|
|
readonly DOT_PORT=853
|
|
|
|
readonly DEFAULT_BIND="10.8.0.1"
|
|
|
|
DOMAIN="$DEFAULT_DOMAIN"
|
|
# Bind to the WireGuard interface, not 0.0.0.0. An internet-facing DoT
|
|
# endpoint is an open resolver: TCP-only so not usable for classic UDP
|
|
# amplification, but still someone else's free DNS and a standing attack
|
|
# surface. Reaching it over the existing tunnel needs no port-forward and no
|
|
# rate limiting, because only configured peers can route to it at all.
|
|
BIND_ADDR="$DEFAULT_BIND"
|
|
CHECK_ONLY=0
|
|
|
|
log() { printf '[dot-resolver] %s\n' "$1"; }
|
|
die() {
|
|
printf '[dot-resolver] ERROR: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $SCRIPT_NAME [OPTIONS]
|
|
--domain <fqdn> DoT hostname (default: $DEFAULT_DOMAIN)
|
|
--bind <addr> Address to listen on (default: $DEFAULT_BIND, the
|
|
WireGuard interface). Use 0.0.0.0 only if you intend
|
|
to expose an open resolver to the internet.
|
|
--check Verify an existing install, change nothing
|
|
-h, --help Show this help
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
require_tools() {
|
|
# Per repo policy a script installs what it needs rather than telling the
|
|
# user to. pacman is non-interactive here; if that fails we stop rather
|
|
# than silently skipping the dependency.
|
|
local missing=()
|
|
command -v stunnel >/dev/null 2>&1 || missing+=(stunnel)
|
|
command -v openssl >/dev/null 2>&1 || missing+=(openssl)
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
log "Installing: ${missing[*]}"
|
|
sudo pacman -S --needed --noconfirm "${missing[@]}" ||
|
|
die "could not install ${missing[*]}; run: sudo pacman -S ${missing[*]}"
|
|
fi
|
|
}
|
|
|
|
verify_prerequisites() {
|
|
# A DoT endpoint in front of a resolver that is not running would answer
|
|
# every query with a connection error, which looks exactly like a network
|
|
# fault on the phone. Check first.
|
|
ss -lnu 2>/dev/null | grep -qE '127\.0\.0\.1:53' ||
|
|
die "dnsmasq is not listening on 127.0.0.1:53 - run setup_dns_blocker.sh first"
|
|
|
|
docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CADDY_CONTAINER" ||
|
|
die "container '$CADDY_CONTAINER' is not running; it owns the certificates"
|
|
|
|
local resolved
|
|
resolved="$(getent hosts "$DOMAIN" | awk '{print $1; exit}')" ||
|
|
die "$DOMAIN does not resolve - DuckDNS wildcard missing?"
|
|
log "$DOMAIN resolves to $resolved"
|
|
|
|
# Binding to an address that does not exist yet makes stunnel fail at
|
|
# start with an opaque error, so check before installing anything.
|
|
if [[ "$BIND_ADDR" != "0.0.0.0" ]]; then
|
|
ip -brief addr show 2>/dev/null | grep -q "$BIND_ADDR" ||
|
|
die "$BIND_ADDR is not assigned to any interface - is wg0 up?"
|
|
log "Binding to $BIND_ADDR (reachable over WireGuard only)"
|
|
else
|
|
log "WARNING: binding to 0.0.0.0 exposes an open resolver publicly"
|
|
fi
|
|
}
|
|
|
|
SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
|
# Installed verbatim to $SYNC_SCRIPT; never generated (see install_cert_sync).
|
|
readonly SYNC_SCRIPT_SRC="$SCRIPT_DIR/../scripts/sync-dot-cert.sh"
|
|
# shellcheck source=lib/dot_resolver_install.sh
|
|
source "$SCRIPT_DIR/lib/dot_resolver_install.sh"
|
|
|
|
main() {
|
|
if [[ $CHECK_ONLY -eq 1 ]]; then
|
|
run_check
|
|
exit $?
|
|
fi
|
|
|
|
require_tools
|
|
verify_prerequisites
|
|
install_cert_sync
|
|
install_stunnel_conf
|
|
install_units
|
|
|
|
log "Syncing certificate..."
|
|
sudo "$SYNC_SCRIPT" "$DOMAIN" "$CADDY_CONTAINER" "$CERT_DIR" || die "certificate sync failed - has Caddy issued $DOMAIN yet?"
|
|
|
|
sudo systemctl enable --now stunnel-dot.service
|
|
sudo systemctl enable --now dot-cert-sync.timer
|
|
open_firewall
|
|
|
|
log "Done. Point the phone at:"
|
|
log " Private DNS hostname: $DOMAIN"
|
|
log "And set in phone_focus_mode/config.sh:"
|
|
log " DNS_TRUSTED_DOT_HOST=\"$DOMAIN\""
|
|
log " DNS_TRUSTED_DOT_IPS=\"\$(getent hosts $DOMAIN | awk '{print \$1}')\""
|
|
run_check || true
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--domain)
|
|
DOMAIN="$2"
|
|
shift 2
|
|
;;
|
|
--bind)
|
|
BIND_ADDR="$2"
|
|
shift 2
|
|
;;
|
|
--check)
|
|
CHECK_ONLY=1
|
|
shift
|
|
;;
|
|
-h | --help) usage ;;
|
|
*) die "Unknown option: $1" ;;
|
|
esac
|
|
done
|
|
|
|
main "$@"
|