Skip to content

Commit 9144323

Browse files
authored
Make the certificate domain configurable (with Pi-hole webserver.domain auto-detection) (#11)
1 parent bf3560e commit 9144323

3 files changed

Lines changed: 174 additions & 15 deletions

File tree

README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ That installs the command to:
2828

2929
Then it runs the setup flow (`local-https --install`) and offers auto-renew (systemd timer recommended).
3030

31+
During the interactive install you are asked for a **domain name** to add to the certificate (default `pi.hole`). Pick whatever your network uses (e.g. `home.lan`, `dns.home`). To set it non-interactively, pass it as an environment variable:
32+
33+
```bash
34+
curl -fsSL https://raw.githubusercontent.com/luizbizzio/local-https/main/install.sh | sudo LOCAL_HTTPS_DOMAIN=home.lan bash
35+
```
36+
3137
-----
3238

3339
## ✨ What it does
@@ -36,7 +42,7 @@ Then it runs the setup flow (`local-https --install`) and offers auto-renew (sys
3642
- 🪪 Issues a **server certificate** (default: **40 days**) with SANs for:
3743
- hostname
3844
- relevant LAN IPs (filtered)
39-
- `pi.hole` when Pi-hole is detected
45+
- a configurable **domain name** (default `pi.hole`)
4046
- Tailscale DNS name when available
4147
- 📦 Generates:
4248
- `server.pem` (cert + key, for services like Pi-hole)
@@ -63,6 +69,31 @@ Then it runs the setup flow (`local-https --install`) and offers auto-renew (sys
6369

6470
-----
6571

72+
## 🌐 Custom domain
73+
74+
The certificate includes a friendly domain name (default `pi.hole`). If your network uses a different local domain, you can configure it. The chosen domain is added to the certificate SANs and is remembered across renewals (stored in the state file).
75+
76+
The domain is resolved with the following precedence (highest first):
77+
78+
1. **Environment variable**`LOCAL_HTTPS_DOMAIN`:
79+
```bash
80+
sudo LOCAL_HTTPS_DOMAIN=home.lan local-https --install
81+
```
82+
2. **CLI flag**`--domain` (works with `--install`, `--configure`, and `--renew`):
83+
```bash
84+
sudo local-https --install --domain home.lan
85+
sudo local-https --configure --domain dns.home
86+
```
87+
3. **Persisted state** – whatever was chosen on the previous run (so renewals stay consistent).
88+
4. **Pi-hole auto-detection** – on a fresh install, if Pi-hole is detected the script reads its configured `webserver.domain` (via `pihole-FTL --config`, falling back to `/etc/pihole/pihole.toml`) and uses that as the default.
89+
5. **Default**`pi.hole`.
90+
91+
During an interactive `--install`/`--configure` you are also prompted, pre-filled with the resolved value (press Enter to keep it).
92+
93+
To change the domain on an existing install, run `sudo local-https --configure --domain <name>` (or `sudo local-https --renew --force-renew --domain <name>`). The Root CA stays the same, so there is nothing new to trust on your devices.
94+
95+
-----
96+
6697
## 🔁 Auto-renew (how it works)
6798

6899
- 📅 The server certificate is issued for **40 days**.

install.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ INSTALL_PATH="/usr/local/sbin/local-https"
1616
SOURCE_URL_DEFAULT="${REPO_RAW_BASE}/local-https.sh"
1717
SOURCE_URL="${LOCAL_HTTPS_SOURCE_URL:-$SOURCE_URL_DEFAULT}"
1818

19-
EXPECTED_SHA256_DEFAULT="4b8578df5a86e9d66ef7911bfce91b4009fb9749864d309f267e34ceb4864733"
19+
EXPECTED_SHA256_DEFAULT="07fe2a6a55de638e07d1699886e48e55a19e2031b084cdfdaca4843733cf6960"
2020
EXPECTED_SHA256="${LOCAL_HTTPS_EXPECTED_SHA256:-$EXPECTED_SHA256_DEFAULT}"
2121

2222
NONINTERACTIVE="${LOCAL_HTTPS_NONINTERACTIVE:-0}"
@@ -118,13 +118,15 @@ run_installer_interactive() {
118118
LOCAL_HTTPS_BOOTSTRAP=1 \
119119
LOCAL_HTTPS_NONINTERACTIVE="$NONINTERACTIVE" \
120120
LOCAL_HTTPS_AUTO_PIHOLE="${LOCAL_HTTPS_AUTO_PIHOLE:-}" \
121+
LOCAL_HTTPS_DOMAIN="${LOCAL_HTTPS_DOMAIN:-}" \
121122
"$INSTALL_PATH" --install
122123
fi
123124

124125
exec env \
125126
LOCAL_HTTPS_BOOTSTRAP=1 \
126127
LOCAL_HTTPS_NONINTERACTIVE=1 \
127128
LOCAL_HTTPS_AUTO_PIHOLE="${LOCAL_HTTPS_AUTO_PIHOLE:-}" \
129+
LOCAL_HTTPS_DOMAIN="${LOCAL_HTTPS_DOMAIN:-}" \
128130
"$INSTALL_PATH" --install
129131
}
130132

local-https.sh

Lines changed: 139 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ INSTALL_PATH="/usr/local/sbin/local-https"
5252
SCRIPT_SOURCE_URL_DEFAULT="https://raw.githubusercontent.com/luizbizzio/local-https/main/local-https.sh"
5353
SCRIPT_SOURCE_URL="${LOCAL_HTTPS_SOURCE_URL:-$SCRIPT_SOURCE_URL_DEFAULT}"
5454

55+
# Friendly domain name added to the certificate SANs (and used as the preferred
56+
# URL when Pi-hole is present). Configurable so it does not have to be "pi.hole".
57+
# Precedence at runtime:
58+
# LOCAL_HTTPS_DOMAIN env > --domain CLI > persisted state > Pi-hole webserver.domain > default.
59+
DOMAIN_DEFAULT="pi.hole"
60+
DOMAIN=""
61+
DOMAIN_CLI=""
62+
DOMAIN_FROM_PIHOLE=0
63+
5564
SSL_DIR="/etc/ssl/servercerts"
5665
CERT_GROUP="certs"
5766

@@ -140,6 +149,87 @@ read_state_value() {
140149
state_unquote "$val" || true
141150
}
142151

152+
sanitize_domain() {
153+
local d="${1:-}"
154+
d="$(printf '%s' "$d" | tr -d '[:space:]' | tr 'A-Z' 'a-z')"
155+
d="${d#.}"
156+
d="${d%.}"
157+
case "$d" in
158+
""|*[!a-z0-9.-]*) printf '%s' ""; return 0 ;;
159+
esac
160+
printf '%s' "$d"
161+
}
162+
163+
detect_pihole_domain() {
164+
local d=""
165+
166+
if command -v pihole-FTL >/dev/null 2>&1; then
167+
d="$(pihole-FTL --config webserver.domain 2>/dev/null | head -n1 || true)"
168+
fi
169+
170+
if [ -z "$d" ] && [ -f "$PIHOLE_TOML" ]; then
171+
d="$(awk '
172+
/^[[:space:]]*\[webserver\][[:space:]]*$/ { inws=1; next }
173+
/^[[:space:]]*\[/ { inws=0 }
174+
inws==1 && /^[[:space:]]*domain[[:space:]]*=/ {
175+
v=$0
176+
sub(/^[^=]*=[[:space:]]*/, "", v)
177+
sub(/[[:space:]]*#.*$/, "", v)
178+
gsub(/^["'"'"']|["'"'"'][[:space:]]*$/, "", v)
179+
print v
180+
exit
181+
}
182+
' "$PIHOLE_TOML" 2>/dev/null || true)"
183+
fi
184+
185+
printf '%s' "$d"
186+
}
187+
188+
resolve_domain() {
189+
local d=""
190+
DOMAIN_FROM_PIHOLE=0
191+
if [ -n "${LOCAL_HTTPS_DOMAIN:-}" ]; then
192+
d="$LOCAL_HTTPS_DOMAIN"
193+
elif [ -n "${DOMAIN_CLI:-}" ]; then
194+
d="$DOMAIN_CLI"
195+
else
196+
d="$(read_state_value domain)"
197+
if [ -z "$d" ] && [ "${PIHOLE_PRESENT:-0}" -eq 1 ]; then
198+
d="$(sanitize_domain "$(detect_pihole_domain)")"
199+
if [ -n "$d" ]; then
200+
DOMAIN_FROM_PIHOLE=1
201+
fi
202+
fi
203+
fi
204+
d="$(sanitize_domain "$d")"
205+
[ -n "$d" ] || d="$DOMAIN_DEFAULT"
206+
DOMAIN="$d"
207+
}
208+
209+
maybe_prompt_domain() {
210+
[ -n "${LOCAL_HTTPS_DOMAIN:-}" ] && return 0
211+
[ -n "${DOMAIN_CLI:-}" ] && return 0
212+
[ "$NONINTERACTIVE" -eq 1 ] && return 0
213+
[ -t 0 ] || return 0
214+
215+
local current="${DOMAIN:-$DOMAIN_DEFAULT}"
216+
local ans="" clean=""
217+
while true; do
218+
read -r -p "$(printf '%b' "\033[36m[?]\033[0m Certificate domain name (e.g. pi.hole, home.lan) [${current}]: ")" ans || ans=""
219+
if [ -z "$ans" ]; then
220+
DOMAIN="$current"
221+
return 0
222+
fi
223+
clean="$(sanitize_domain "$ans")"
224+
if [ -n "$clean" ]; then
225+
DOMAIN="$clean"
226+
out "\033[32m[✓]\033[0m Using domain: $DOMAIN"
227+
return 0
228+
fi
229+
out "\033[33m[!]\033[0m Invalid domain. Use letters, digits, dots, and hyphens."
230+
done
231+
}
232+
143233
svc_active() {
144234
local name="$1"
145235
if has_systemctl; then
@@ -356,6 +446,7 @@ write_state() {
356446
echo "installed_at=$(sh_quote "$installed_at")"
357447
echo "last_run_at=$(sh_quote "$ts")"
358448
echo "hostname=$(sh_quote "${HOSTNAME:-$(hostname 2>/dev/null || true)}")"
449+
echo "domain=$(sh_quote "${DOMAIN:-$DOMAIN_DEFAULT}")"
359450
echo "applied_targets=$(sh_quote "$applied")"
360451
echo "autorenew_method=$(sh_quote "$method")"
361452
echo "pihole_detected=$(sh_quote "${PIHOLE_PRESENT:-0}")"
@@ -483,20 +574,23 @@ remove_block() {
483574
print_help() {
484575
echo ""
485576
echo "Usage:"
486-
echo " $SCRIPT_CMD_NAME --install"
487-
echo " $SCRIPT_CMD_NAME --renew [--force-renew]"
577+
echo " $SCRIPT_CMD_NAME --install [--domain <name>]"
578+
echo " $SCRIPT_CMD_NAME --renew [--force-renew] [--domain <name>]"
488579
echo " $SCRIPT_CMD_NAME --check"
489580
echo " $SCRIPT_CMD_NAME --status"
490581
echo " $SCRIPT_CMD_NAME --print-ca"
491582
echo " $SCRIPT_CMD_NAME --print-pfx-pass"
492583
echo " $SCRIPT_CMD_NAME --rotate-pfx-pass"
493-
echo " $SCRIPT_CMD_NAME --configure"
584+
echo " $SCRIPT_CMD_NAME --configure [--domain <name>]"
494585
echo " $SCRIPT_CMD_NAME --uninstall [--yes] [--purge-certs]"
495586
echo ""
496587
echo "Notes:"
497588
echo " - Running without args shows this help."
498589
echo " - If already installed, --install will not run again."
499590
echo " - Reinstall only via: --uninstall then --install"
591+
echo " - --domain sets the friendly name added to the certificate (default: $DOMAIN_DEFAULT)."
592+
echo " It is remembered across renewals. You can also set LOCAL_HTTPS_DOMAIN."
593+
echo " If Pi-hole is detected and no domain is set, its webserver.domain is used."
500594
echo ""
501595
}
502596

@@ -715,6 +809,13 @@ read_host_identity() {
715809
HOSTNAME="$(hostname 2>/dev/null || true)"
716810
[ -n "$HOSTNAME" ] || HOSTNAME="localhost"
717811

812+
resolve_domain
813+
if [ "$DOMAIN_FROM_PIHOLE" -eq 1 ]; then
814+
out "\033[34m[i]\033[0m Detected Pi-hole web domain: $DOMAIN"
815+
else
816+
vout "\033[34m[i]\033[0m Domain: $DOMAIN"
817+
fi
818+
718819
collect_san_ips_from_hostname_i
719820

720821
local ip_count=0
@@ -807,6 +908,7 @@ issue_or_renew_server_cert() {
807908

808909
[ -n "${HOSTNAME:-}" ] || HOSTNAME="$(hostname 2>/dev/null || true)"
809910
[ -n "${HOSTNAME:-}" ] || HOSTNAME="localhost"
911+
[ -n "${DOMAIN:-}" ] || resolve_domain
810912

811913
cat << EOF > server-openssl.cnf
812914
[ req ]
@@ -828,18 +930,19 @@ DNS.1 = $HOSTNAME
828930
EOF
829931

830932
local DNS_INDEX=2
831-
832-
if [ "$HOSTNAME" != "pi.hole" ]; then
833-
echo "DNS.${DNS_INDEX} = pi.hole" >> server-openssl.cnf
933+
local DOMAIN_SHORT="${DOMAIN%%.*}"
934+
935+
if [ -n "$DOMAIN" ] && [ "$HOSTNAME" != "$DOMAIN" ]; then
936+
echo "DNS.${DNS_INDEX} = $DOMAIN" >> server-openssl.cnf
834937
DNS_INDEX=$((DNS_INDEX + 1))
835938
fi
836-
837-
if [ -n "$TAILSCALE_DNS" ] && [ "$TAILSCALE_DNS" != "null" ] && [ "$TAILSCALE_DNS" != "pi.hole" ] && [ "$TAILSCALE_DNS" != "$HOSTNAME" ]; then
939+
940+
if [ -n "$TAILSCALE_DNS" ] && [ "$TAILSCALE_DNS" != "null" ] && [ "$TAILSCALE_DNS" != "$DOMAIN" ] && [ "$TAILSCALE_DNS" != "$HOSTNAME" ]; then
838941
echo "DNS.${DNS_INDEX} = $TAILSCALE_DNS" >> server-openssl.cnf
839942
DNS_INDEX=$((DNS_INDEX + 1))
840943
fi
841-
842-
if [ -n "$TAILSCALE_SHORT" ] && [ "$TAILSCALE_SHORT" != "null" ] && [ "$TAILSCALE_SHORT" != "$HOSTNAME" ] && [ "$TAILSCALE_SHORT" != "pi" ] && [ "$TAILSCALE_SHORT" != "pihole" ]; then
944+
945+
if [ -n "$TAILSCALE_SHORT" ] && [ "$TAILSCALE_SHORT" != "null" ] && [ "$TAILSCALE_SHORT" != "$HOSTNAME" ] && [ "$TAILSCALE_SHORT" != "$DOMAIN_SHORT" ]; then
843946
echo "DNS.${DNS_INDEX} = $TAILSCALE_SHORT" >> server-openssl.cnf
844947
DNS_INDEX=$((DNS_INDEX + 1))
845948
fi
@@ -907,8 +1010,8 @@ EOF
9071010
for _ip in $FILTERED_IPS; do ip_count=$((ip_count + 1)); done
9081011

9091012
local dns_list="$HOSTNAME"
910-
if [ "$PIHOLE_PRESENT" -eq 1 ]; then
911-
dns_list="$dns_list, pi.hole"
1013+
if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "$HOSTNAME" ]; then
1014+
dns_list="$dns_list, $DOMAIN"
9121015
fi
9131016
if [ -n "$TAILSCALE_DNS" ] && [ "$TAILSCALE_DNS" != "null" ]; then
9141017
dns_list="$dns_list, $TAILSCALE_DNS"
@@ -1158,7 +1261,7 @@ deploy_pihole_ftl_tls() {
11581261
choose_preferred_dns() {
11591262
local d=""
11601263
if [ "${PIHOLE_PRESENT:-0}" -eq 1 ]; then
1161-
d="pi.hole"
1264+
d="${DOMAIN:-$DOMAIN_DEFAULT}"
11621265
fi
11631266
if [ -z "$d" ] && [ -n "${TAILSCALE_DNS:-}" ] && [ "${TAILSCALE_DNS:-}" != "null" ]; then
11641267
d="$TAILSCALE_DNS"
@@ -1755,6 +1858,7 @@ status() {
17551858
echo "- State file: present ($STATE_FILE)"
17561859
echo "- Installed at: $(read_state_value installed_at)"
17571860
echo "- Last run at: $(read_state_value last_run_at)"
1861+
echo "- Domain: $(read_state_value domain)"
17581862
echo "- Applied targets: $(read_state_value applied_targets)"
17591863
echo "- Auto renew method: $(read_state_value autorenew_method)"
17601864
echo "- Cert renewed last run: $(read_state_value cert_renewed)"
@@ -1908,6 +2012,8 @@ renew_flow() {
19082012
HOSTNAME="$(hostname 2>/dev/null || true)"
19092013
[ -n "$HOSTNAME" ] || HOSTNAME="localhost"
19102014

2015+
resolve_domain
2016+
19112017
collect_san_ips_from_hostname_i
19122018

19132019
TAILSCALE_DNS=""
@@ -2015,6 +2121,7 @@ configure_flow() {
20152121

20162122
detect_pihole_and_technitium
20172123
read_host_identity
2124+
maybe_prompt_domain
20182125
prepare_dir
20192126

20202127
[ -f "$CA_CRT" ] && [ -f "$CA_KEY" ] || create_or_reuse_ca
@@ -2068,6 +2175,7 @@ install_flow() {
20682175

20692176
detect_pihole_and_technitium
20702177
read_host_identity
2178+
maybe_prompt_domain
20712179
prepare_dir
20722180
create_or_reuse_ca
20732181
issue_or_renew_server_cert
@@ -2094,6 +2202,14 @@ parse_cli() {
20942202
;;
20952203
--install)
20962204
shift
2205+
while [ "$#" -gt 0 ]; do
2206+
case "$1" in
2207+
--domain) DOMAIN_CLI="${2:-}"; shift 2 || shift; continue ;;
2208+
--domain=*) DOMAIN_CLI="${1#*=}" ;;
2209+
*) ;;
2210+
esac
2211+
shift
2212+
done
20972213
install_flow
20982214
exit 0
20992215
;;
@@ -2102,6 +2218,8 @@ parse_cli() {
21022218
while [ "$#" -gt 0 ]; do
21032219
case "$1" in
21042220
--force-renew) FORCE_RENEW=1 ;;
2221+
--domain) DOMAIN_CLI="${2:-}"; shift 2 || shift; continue ;;
2222+
--domain=*) DOMAIN_CLI="${1#*=}" ;;
21052223
*) ;;
21062224
esac
21072225
shift
@@ -2131,6 +2249,14 @@ parse_cli() {
21312249
;;
21322250
--configure)
21332251
shift
2252+
while [ "$#" -gt 0 ]; do
2253+
case "$1" in
2254+
--domain) DOMAIN_CLI="${2:-}"; shift 2 || shift; continue ;;
2255+
--domain=*) DOMAIN_CLI="${1#*=}" ;;
2256+
*) ;;
2257+
esac
2258+
shift
2259+
done
21342260
configure_flow
21352261
exit 0
21362262
;;

0 commit comments

Comments
 (0)