Skip to content

Commit c195e29

Browse files
committed
Add self-update support and improve install/update flow
1 parent 9fb520c commit c195e29

3 files changed

Lines changed: 285 additions & 27 deletions

File tree

‎README.md‎

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,15 @@ It can automatically deploy certificates and reload supported services:
1616

1717
It is officially supported on Debian-based distributions (Debian, Ubuntu, Raspberry Pi OS, Armbian). Other Linux distributions may work but are not officially supported.
1818

19-
## 🚀 One-Step Automated Install
19+
## 🚀 One-Step Install / Update
2020

2121
```bash
2222
curl -fsSL https://raw.githubusercontent.com/luizbizzio/local-https/main/install.sh | sudo bash
2323
```
2424

25-
That installs the command to:
25+
The installer downloads and verifies the latest `local-https`, then installs it to `/usr/local/sbin/local-https`.
2626

27-
- `/usr/local/sbin/local-https`
28-
29-
Then it runs the setup flow (`local-https --install`) and offers auto-renew (systemd timer recommended).
30-
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-
```
27+
On a new installation, or if the initial setup was never completed, it asks whether you want to start the setup. If `local-https` is already configured, running the same command only updates the program and keeps your existing certificates and configuration.
3628

3729
-----
3830

@@ -48,6 +40,7 @@ curl -fsSL https://raw.githubusercontent.com/luizbizzio/local-https/main/install
4840
- `server.pem` (cert + key, for services like Pi-hole)
4941
- `server.pfx` (password-protected, for services like Technitium)
5042
- 🔁 Can enable **automatic renewal** via **systemd timer** (or cron fallback)
43+
- ⬆️ Can check GitHub for a newer version and update itself with `--update`
5144
- 🧠 On renewal, it only “deploys/restarts” if a new cert was actually created (unless forced)
5245

5346
-----
@@ -56,18 +49,50 @@ curl -fsSL https://raw.githubusercontent.com/luizbizzio/local-https/main/install
5649

5750
| Command | What it does |
5851
|---|---|
59-
| `local-https --version` | Prints the installed `local-https` version |
52+
| `local-https --version` / `--v` / `-V` | Prints the installed `local-https` version |
53+
| `sudo local-https --update` | Checks GitHub for a newer version and asks before updating |
54+
| `sudo local-https --update --yes` | Updates without asking for confirmation |
6055
| `sudo local-https --install` | Full setup (Root CA, server cert, PEM/PFX, permissions, auto-renew, optional Pi-hole deploy, Technitium TLS if detected) |
6156
| `sudo local-https --renew` | Renew if needed (near expiry window). If nothing to do, exits fast |
6257
| `sudo local-https --renew --force-renew` | Forces a new server certificate + rebuilds PFX + restarts detected services |
63-
| `sudo local-https --status` | Shows current status and last run info |
58+
| `sudo local-https --status` | Shows the installed version, current status, and last run info |
6459
| `sudo local-https --check` | Exit code indicates if renewal is needed |
6560
| `sudo local-https --configure` | Re-run deploy steps for Pi-hole / Technitium without reinstalling everything |
6661
| `sudo local-https --print-ca` | Prints `rootCA.crt` (useful to copy to devices) |
6762
| `sudo local-https --print-pfx-pass` | Prints the PFX password (stored in a root-only file) |
6863
| `sudo local-https --rotate-pfx-pass` | Rotates PFX password, rebuilds PFX, updates Technitium TLS settings |
6964
| `sudo local-https --uninstall [--yes] [--purge-certs]` | Removes installed files and optionally deletes generated certs |
7065

66+
### ⬆️ Updating later
67+
68+
Once installed, you can check for a newer version without using the full `curl` command again:
69+
70+
```bash
71+
sudo local-https --update
72+
```
73+
74+
If a newer version is available, `local-https` shows the current and latest versions and asks before installing it. The update only replaces the program itself; your existing Root CA, certificates, state, and service configuration are kept.
75+
76+
For non-interactive updates:
77+
78+
```bash
79+
sudo local-https --update --yes
80+
```
81+
82+
-----
83+
84+
## ⚙️ Reconfigure services
85+
86+
Use `--configure` when you want to re-detect and reapply supported service integrations without reinstalling `local-https`.
87+
88+
```bash
89+
sudo local-https --configure
90+
```
91+
92+
This re-checks Pi-hole and Technitium, reapplies certificate permissions and TLS deployment, and keeps your existing Root CA and installation.
93+
94+
This is useful after installing or changing Pi-hole or Technitium, or when you want to run the integration setup again.
95+
7196
-----
7297

7398
## 🌐 Custom domain
@@ -138,7 +163,7 @@ sudo systemd-run --unit=local-https-renew-force --service-type=oneshot /usr/lo
138163
- If Technitium is detected, the script can configure Technitium Web UI TLS to use:
139164
- `server.pfx` + the stored password
140165
- Supports both legacy Technitium Linux installations running as root and newer non-root installations.
141-
- For non-root installations, the Technitium service user is automatically granted access to the `certs` group when required.
166+
- If the Technitium `dns-server` account exists, it is automatically granted access to the `certs` group. Custom non-root Technitium service users are supported as well.
142167
- If the Technitium systemd service restricts filesystem access, `local-https` automatically creates a systemd drop-in granting read-only access to `server.pfx`.
143168
- On renew (when a new cert is created), it restarts the Technitium service so the new cert is loaded.
144169

‎install.sh‎

Lines changed: 106 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
1010

1111
REPO_RAW_BASE_DEFAULT="https://raw.githubusercontent.com/luizbizzio/local-https/main"
1212
REPO_RAW_BASE="${LOCAL_HTTPS_RAW_BASE:-$REPO_RAW_BASE_DEFAULT}"
13-
1413
SCRIPT_NAME="local-https"
1514
INSTALL_PATH="/usr/local/sbin/local-https"
1615
SOURCE_URL_DEFAULT="${REPO_RAW_BASE}/local-https.sh"
1716
SOURCE_URL="${LOCAL_HTTPS_SOURCE_URL:-$SOURCE_URL_DEFAULT}"
1817

19-
EXPECTED_SHA256_DEFAULT="dd9404dbdbfa30fc019a35688fed4e0134ce3a02e32387eb75df480217389e8a"
18+
EXPECTED_SHA256_DEFAULT="506f6d21061f80faba2aa7a8c8207f1b36485bd0c8ea9eaed7eb975b5b13abe8"
2019
EXPECTED_SHA256="${LOCAL_HTTPS_EXPECTED_SHA256:-$EXPECTED_SHA256_DEFAULT}"
2120

2221
NONINTERACTIVE="${LOCAL_HTTPS_NONINTERACTIVE:-0}"
22+
UPDATE_ONLY="${LOCAL_HTTPS_UPDATE_ONLY:-0}"
2323
case "$NONINTERACTIVE" in 1|true|TRUE|yes|YES) NONINTERACTIVE=1 ;; *) NONINTERACTIVE=0 ;; esac
24-
24+
case "$UPDATE_ONLY" in 1|true|TRUE|yes|YES) UPDATE_ONLY=1 ;; *) UPDATE_ONLY=0 ;; esac
2525
out() { printf '%b\n' "$1"; }
2626
die() { printf '%b\n' "\033[31m[ERROR]\033[0m $1\n" >&2; exit 1; }
2727
need_cmd() { command -v "$1" >/dev/null 2>&1 || die "Missing dependency: $1"; }
@@ -44,7 +44,6 @@ curl_fetch() {
4444
-o "$out_file" \
4545
"$url"
4646
}
47-
4847
sha256_of_file() {
4948
local f="$1"
5049
if command -v sha256sum >/dev/null 2>&1; then
@@ -66,7 +65,6 @@ verify_sha256_if_set() {
6665
local f="$1"
6766

6867
[ -n "$EXPECTED_SHA256" ] || return 0
69-
7068
local got=""
7169
got="$(sha256_of_file "$f" 2>/dev/null || true)"
7270
[ -n "$got" ] || die "Cannot compute SHA-256."
@@ -84,13 +82,24 @@ sanity_check_script() {
8482
[ "$(wc -c < "$f")" -ge 2000 ] || die "Downloaded file too small."
8583

8684
head -n 1 "$f" | grep -Eq '^#!' || die "Missing shebang."
87-
8885
if head -n 5 "$f" | grep -Eqi '<!doctype html|<html|404'; then
8986
die "Downloaded content looks like HTML."
9087
fi
9188

9289
grep -qE 'SCRIPT_CMD_NAME="local-https"|parse_cli' "$f" || \
9390
die "Downloaded file does not look like local-https.sh."
91+
92+
bash -n "$f" >/dev/null 2>&1 || die "Downloaded local-https.sh failed syntax validation."
93+
}
94+
95+
script_version_from_file() {
96+
local f="$1"
97+
awk -F'"' '/^[[:space:]]*VERSION="[0-9]+\.[0-9]+\.[0-9]+"/ { print $2; exit }' "$f" 2>/dev/null || true
98+
}
99+
100+
installed_version() {
101+
[ -x "$INSTALL_PATH" ] || return 0
102+
"$INSTALL_PATH" --version 2>/dev/null | awk '{ print $NF; exit }' || true
94103
}
95104

96105
install_atomic() {
@@ -109,7 +118,6 @@ install_atomic() {
109118
mv -f "$tmpdst" "$dst"
110119
}
111120

112-
113121
run_installer_interactive() {
114122
out "\033[36m[INFO]\033[0m Running: local-https --install"
115123

@@ -121,7 +129,6 @@ run_installer_interactive() {
121129
LOCAL_HTTPS_DOMAIN="${LOCAL_HTTPS_DOMAIN:-}" \
122130
"$INSTALL_PATH" --install
123131
fi
124-
125132
exec env \
126133
LOCAL_HTTPS_BOOTSTRAP=1 \
127134
LOCAL_HTTPS_NONINTERACTIVE=1 \
@@ -130,6 +137,31 @@ run_installer_interactive() {
130137
"$INSTALL_PATH" --install
131138
}
132139

140+
prompt_run_setup() {
141+
if [ "$NONINTERACTIVE" -eq 1 ]; then
142+
run_installer_interactive
143+
fi
144+
145+
if [ ! -r /dev/tty ] || [ ! -w /dev/tty ]; then
146+
out "\033[34m[INFO]\033[0m No interactive terminal detected. Running setup non-interactively."
147+
run_installer_interactive
148+
fi
149+
150+
local answer=""
151+
printf '%b' "\033[36m[?]\033[0m Run the initial setup now? (Y/n): " >/dev/tty
152+
read -r answer </dev/tty || answer=""
153+
154+
case "$answer" in
155+
n|N|no|NO)
156+
out "\033[34m[INFO]\033[0m Setup skipped."
157+
out "\033[34m[INFO]\033[0m Run it later with: sudo local-https --install"
158+
;;
159+
*)
160+
run_installer_interactive
161+
;;
162+
esac
163+
}
164+
133165
main() {
134166
require_root
135167
need_cmd curl
@@ -143,11 +175,26 @@ main() {
143175
need_cmd mv
144176
need_cmd chmod
145177
need_cmd rm
146-
178+
need_cmd bash
147179
local tmp=""
148-
cleanup() { [ -n "$tmp" ] && rm -f "$tmp" >/dev/null 2>&1 || true; }
180+
cleanup() { [ -n "${tmp:-}" ] && rm -f "$tmp" >/dev/null 2>&1 || true; }
149181
trap cleanup EXIT
150182

183+
local had_binary=0
184+
local was_configured=0
185+
local current_version=""
186+
local target_version=""
187+
188+
[ -f "$INSTALL_PATH" ] && had_binary=1
189+
190+
if [ -f "/var/lib/local-https/installed" ] || [ -f "/var/lib/local-https/state.env" ]; then
191+
was_configured=1
192+
fi
193+
194+
if [ "$had_binary" -eq 1 ]; then
195+
current_version="$(installed_version)"
196+
fi
197+
151198
out "\033[36m[INFO]\033[0m Downloading: $SOURCE_URL"
152199

153200
tmp="$(tmpfile_make)"
@@ -156,12 +203,58 @@ main() {
156203
verify_sha256_if_set "$tmp"
157204
sanity_check_script "$tmp"
158205

159-
out "\033[36m[INFO]\033[0m Installing: $INSTALL_PATH"
206+
target_version="$(script_version_from_file "$tmp")"
207+
208+
if [ "$had_binary" -eq 1 ]; then
209+
if [ -n "$current_version" ] && [ -n "$target_version" ] && [ "$current_version" = "$target_version" ]; then
210+
out "\033[36m[INFO]\033[0m Reinstalling local-https v${target_version}..."
211+
elif [ -n "$current_version" ] && [ -n "$target_version" ]; then
212+
out "\033[36m[INFO]\033[0m Updating local-https v${current_version} -> v${target_version}..."
213+
elif [ -n "$target_version" ]; then
214+
out "\033[36m[INFO]\033[0m Updating local-https to v${target_version}..."
215+
else
216+
out "\033[36m[INFO]\033[0m Updating: $INSTALL_PATH"
217+
fi
218+
else
219+
if [ -n "$target_version" ]; then
220+
out "\033[36m[INFO]\033[0m Installing local-https v${target_version}..."
221+
else
222+
out "\033[36m[INFO]\033[0m Installing: $INSTALL_PATH"
223+
fi
224+
fi
225+
160226
install_atomic "$tmp" "$INSTALL_PATH"
227+
rm -f "$tmp" >/dev/null 2>&1 || true
228+
tmp=""
229+
230+
if [ "$had_binary" -eq 1 ]; then
231+
out "\033[32m[OK]\033[0m Updated: $INSTALL_PATH"
232+
else
233+
out "\033[32m[OK]\033[0m Installed: $INSTALL_PATH"
234+
fi
161235

162-
out "\033[32m[OK]\033[0m Installed: $INSTALL_PATH"
236+
"$INSTALL_PATH" --version 2>/dev/null || true
237+
238+
if [ "$UPDATE_ONLY" -eq 1 ]; then
239+
if [ "$was_configured" -eq 1 ]; then
240+
out "\033[34m[INFO]\033[0m Existing certificates and configuration were kept."
241+
else
242+
out "\033[34m[INFO]\033[0m Program updated. Initial setup was not changed."
243+
fi
244+
exit 0
245+
fi
246+
247+
if [ "$was_configured" -eq 1 ]; then
248+
out "\033[34m[INFO]\033[0m Existing certificates and configuration were kept."
249+
exit 0
250+
fi
251+
252+
if [ "$had_binary" -eq 1 ]; then
253+
out "\033[34m[INFO]\033[0m No completed setup was detected."
254+
fi
163255

164-
run_installer_interactive
256+
echo ""
257+
prompt_run_setup
165258
}
166259

167260
main "$@"

0 commit comments

Comments
 (0)