Skip to content

Commit f306e0a

Browse files
committed
feat: add one-shot updater to latest release
1 parent 085619a commit f306e0a

3 files changed

Lines changed: 170 additions & 0 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ bash bootstrap.sh --media-dir /Volumes/T9/Media --install-dir ~/mac-media-stack
7373
bash bootstrap.sh --jellyfin # use Jellyfin instead of Plex
7474
```
7575

76+
## Update Existing Clone
77+
78+
Already cloned an older version and want the latest release tag without reinstalling?
79+
80+
One-liner (run inside your existing clone directory):
81+
82+
```bash
83+
bash <(curl -fsSL https://raw.githubusercontent.com/liamvibecodes/mac-media-stack/main/scripts/update-to-latest-release.sh)
84+
```
85+
86+
Local script (once present):
87+
88+
```bash
89+
bash scripts/update-to-latest-release.sh
90+
```
91+
7692
<details>
7793
<summary>See it in action</summary>
7894
<br>
@@ -119,6 +135,7 @@ By default, Seerr is bound to `127.0.0.1` for safer local-only access. Set `SEER
119135
| `scripts/health-check.sh` | Checks if everything is running correctly |
120136
| `scripts/auto-heal.sh` | Hourly self-healer (restarts VPN/containers if down) |
121137
| `scripts/install-auto-heal.sh` | Installs auto-heal as a background job via launchd |
138+
| `scripts/update-to-latest-release.sh` | Updates an older clone to the latest tagged release safely |
122139
| `scripts/refresh-image-lock.sh` | Refreshes pinned image digests and regenerates IMAGE_LOCK.md |
123140

124141
## What It Looks Like

SETUP.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ To use Jellyfin instead of Plex:
3333
bash bootstrap.sh --jellyfin
3434
```
3535

36+
Already on an older clone and want the newest release tag:
37+
38+
```bash
39+
bash <(curl -fsSL https://raw.githubusercontent.com/liamvibecodes/mac-media-stack/main/scripts/update-to-latest-release.sh)
40+
```
41+
3642
---
3743

3844
## Choose Your Media Server
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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: docker compose up -d"
147+
echo " 3. Verify: bash scripts/health-check.sh"

0 commit comments

Comments
 (0)