Skip to content

Commit d3d94de

Browse files
committed
feat: add one-shot updater to latest release
1 parent 392166f commit d3d94de

3 files changed

Lines changed: 173 additions & 0 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,22 @@ To use Jellyfin instead of Plex:
176176
bash bootstrap.sh --jellyfin
177177
```
178178

179+
## Update Existing Clone
180+
181+
Already cloned an older version and want the latest release tag without reinstalling?
182+
183+
One-liner (run inside your existing clone directory):
184+
185+
```bash
186+
bash <(curl -fsSL https://raw.githubusercontent.com/liamvibecodes/mac-media-stack-advanced/main/scripts/update-to-latest-release.sh)
187+
```
188+
189+
Local script (once present):
190+
191+
```bash
192+
bash scripts/update-to-latest-release.sh
193+
```
194+
179195
## Upgrading From Basic
180196

181197
Already running `mac-media-stack` and want to keep your existing library/configs?
@@ -268,6 +284,7 @@ bash scripts/vpn-mode.sh nord
268284
| `scripts/auto-heal.sh` | Hourly self-healer |
269285
| `scripts/backup.sh` | Config and database backup |
270286
| `scripts/download-watchdog.py` | Stalled torrent detection and auto-fix |
287+
| `scripts/update-to-latest-release.sh` | Updates an older clone to the latest tagged release safely |
271288
| `scripts/setup-tdarr-native.sh` | Installs/updates native Tdarr and launchd services |
272289
| `scripts/tdarr-apply-quality-flow.sh` | Loads quality-first HEVC flow preset into Tdarr DB |
273290
| `scripts/vpn-mode.sh` | Manual VPN provider switcher |

SETUP.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ To run from a local clone with custom paths:
2222
bash bootstrap.sh --media-dir /Volumes/T9/Media --install-dir ~/mac-media-stack-advanced
2323
```
2424

25+
Already on an older clone and want the newest release tag:
26+
27+
```bash
28+
bash <(curl -fsSL https://raw.githubusercontent.com/liamvibecodes/mac-media-stack-advanced/main/scripts/update-to-latest-release.sh)
29+
```
30+
2531
Already running the basic stack and want an in-place migration? Use [UPGRADE.md](UPGRADE.md).
2632

2733
---
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/bin/bash
2+
# Update an existing clone to the latest tagged release (vX.Y.Z).
3+
# Safe by default: refuses to run with tracked local changes or local commits ahead of upstream.
4+
# Usage: bash scripts/update-to-latest-release.sh [--yes] [--force] [--skip-setup]
5+
6+
set -euo pipefail
7+
8+
RED='\033[0;31m'
9+
GREEN='\033[0;32m'
10+
YELLOW='\033[1;33m'
11+
CYAN='\033[0;36m'
12+
NC='\033[0m'
13+
14+
ASSUME_YES=false
15+
FORCE=false
16+
SKIP_SETUP=false
17+
18+
usage() {
19+
cat <<EOF
20+
Usage: bash scripts/update-to-latest-release.sh [OPTIONS]
21+
22+
Updates this clone to the latest Git tag that matches v*.
23+
24+
Options:
25+
--yes Skip confirmation prompt
26+
--force Allow resetting when local branch is ahead of upstream
27+
--skip-setup Do not run scripts/setup.sh after updating
28+
--help Show this help message
29+
EOF
30+
}
31+
32+
log() { echo -e "${GREEN}OK${NC} $1"; }
33+
warn() { echo -e "${YELLOW}WARN${NC} $1"; }
34+
info() { echo -e "${CYAN}..${NC} $1"; }
35+
fail() { echo -e "${RED}FAIL${NC} $1"; exit 1; }
36+
37+
while [[ $# -gt 0 ]]; do
38+
case "$1" in
39+
--yes)
40+
ASSUME_YES=true
41+
shift
42+
;;
43+
--force)
44+
FORCE=true
45+
shift
46+
;;
47+
--skip-setup)
48+
SKIP_SETUP=true
49+
shift
50+
;;
51+
--help|-h)
52+
usage
53+
exit 0
54+
;;
55+
*)
56+
echo "Unknown option: $1"
57+
usage
58+
exit 1
59+
;;
60+
esac
61+
done
62+
63+
command -v git >/dev/null 2>&1 || fail "git not found"
64+
65+
PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || true)"
66+
[[ -n "$PROJECT_DIR" ]] || fail "Not inside a git repository"
67+
cd "$PROJECT_DIR"
68+
69+
origin_url="$(git remote get-url origin 2>/dev/null || true)"
70+
[[ -n "$origin_url" ]] || fail "origin remote not configured"
71+
72+
if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
73+
fail "Tracked local changes detected. Commit/stash first, or rerun with --force."
74+
fi
75+
76+
current_branch="$(git symbolic-ref --quiet --short HEAD || true)"
77+
if [[ -n "$current_branch" ]]; then
78+
upstream="$(git for-each-ref --format='%(upstream:short)' "refs/heads/$current_branch" | head -1)"
79+
if [[ -n "$upstream" ]]; then
80+
ahead_count="$(git rev-list --count "$upstream..$current_branch")"
81+
if [[ "$ahead_count" -gt 0 && "$FORCE" != true ]]; then
82+
fail "Branch '$current_branch' has $ahead_count local commit(s) ahead of upstream. Rerun with --force to reset."
83+
fi
84+
fi
85+
fi
86+
87+
info "Fetching tags from origin..."
88+
git fetch origin --tags --prune >/dev/null
89+
90+
latest_tag="$(git tag -l 'v*' --sort=-version:refname | head -1)"
91+
[[ -n "$latest_tag" ]] || fail "No release tags matching v* were found"
92+
93+
target_sha="$(git rev-list -n1 "$latest_tag")"
94+
current_sha="$(git rev-parse HEAD)"
95+
96+
echo ""
97+
echo "Repo: $PROJECT_DIR"
98+
echo "Origin: $origin_url"
99+
echo "Current ref: ${current_branch:-detached}"
100+
echo "Current SHA: $current_sha"
101+
echo "Latest tag: $latest_tag"
102+
echo "Target SHA: $target_sha"
103+
echo ""
104+
105+
if [[ "$current_sha" == "$target_sha" ]]; then
106+
log "Already on latest release ($latest_tag)"
107+
else
108+
if [[ "$ASSUME_YES" != true ]]; then
109+
read -r -p "Update this clone to $latest_tag? [y/N] " confirm
110+
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
111+
echo "Cancelled."
112+
exit 0
113+
fi
114+
fi
115+
116+
if [[ -z "$current_branch" ]]; then
117+
warn "Detached HEAD detected. Checking out 'main' before reset."
118+
if git show-ref --verify --quiet refs/heads/main; then
119+
git checkout main >/dev/null
120+
else
121+
git checkout -b main >/dev/null
122+
fi
123+
current_branch="main"
124+
fi
125+
126+
git reset --hard "$target_sha" >/dev/null
127+
log "Updated $current_branch to $latest_tag"
128+
fi
129+
130+
if [[ "$SKIP_SETUP" != true && -x scripts/setup.sh ]]; then
131+
media_dir="$(sed -n 's/^MEDIA_DIR=//p' .env 2>/dev/null | head -1)"
132+
media_dir="${media_dir/#\~/$HOME}"
133+
info "Running setup sync (scripts/setup.sh)"
134+
if [[ -n "$media_dir" ]]; then
135+
bash scripts/setup.sh --media-dir "$media_dir" >/dev/null
136+
else
137+
bash scripts/setup.sh >/dev/null
138+
fi
139+
log "Setup sync complete"
140+
fi
141+
142+
echo ""
143+
echo "Update complete."
144+
echo "Next steps:"
145+
echo " 1. Review .env if new keys were added"
146+
echo " 2. Restart stack:"
147+
echo " - docker compose up -d"
148+
echo " - if MEDIA_SERVER=jellyfin: docker compose --profile jellyfin up -d"
149+
echo " - if TDARR_MODE=docker: docker compose --profile tdarr-docker up -d"
150+
echo " 3. Verify: bash scripts/health-check.sh"

0 commit comments

Comments
 (0)