Skip to content

Commit 6cf20be

Browse files
committed
Unify media path handling and reduce setup drift
1 parent e8dc062 commit 6cf20be

8 files changed

Lines changed: 165 additions & 89 deletions

File tree

.github/workflows/validate.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ jobs:
1818
bash -n "$f"
1919
done < <(find scripts -type f -name '*.sh' -print0)
2020
21+
- name: Validate media path usage
22+
run: |
23+
# Automation scripts must resolve MEDIA_DIR from env/.env, not hardcode $HOME/Media.
24+
! rg -n '\$HOME/Media' scripts/auto-heal.sh scripts/install-auto-heal.sh
25+
2126
- name: Validate compose config
2227
run: |
2328
cp .env.example .env

SETUP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ The script will:
191191
- Connect everything together (Prowlarr, Radarr, Sonarr, Seerr)
192192
- Ask you to sign in to Seerr with Plex (one browser click)
193193

194-
At the end it will print your qBittorrent password and save credentials/API keys to `~/Media/state/first-run-credentials.txt` (mode `600`).
194+
At the end it will print your qBittorrent password and save credentials/API keys to `<MEDIA_DIR>/state/first-run-credentials.txt` (mode `600`, default path `~/Media/state/first-run-credentials.txt`).
195195

196196
---
197197
## Step 9: Install Auto-Healer (Optional but Recommended)
@@ -202,7 +202,7 @@ This installs a background job that checks your stack every hour. If the VPN goe
202202
bash scripts/install-auto-heal.sh
203203
```
204204

205-
Logs go to `~/Media/logs/auto-heal.log` if you ever want to check what it's been doing.
205+
Logs go to `<MEDIA_DIR>/logs/auto-heal.log` (default `~/Media/logs/auto-heal.log`) if you ever want to check what it's been doing.
206206

207207
To remove it later:
208208
```bash

bootstrap.sh

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ YELLOW='\033[1;33m'
1010
CYAN='\033[0;36m'
1111
NC='\033[0m'
1212

13+
BOOTSTRAP_DIR="$(cd "$(dirname "$0")" && pwd)"
1314
MEDIA_DIR="$HOME/Media"
1415
INSTALL_DIR="$HOME/mac-media-stack"
1516
NON_INTERACTIVE=false
@@ -75,59 +76,64 @@ echo "=============================="
7576
echo ""
7677

7778
# Detect container runtime
78-
detect_installed_runtime() {
79-
local has_orbstack=0
80-
local has_docker_desktop=0
81-
82-
if [[ -d "/Applications/OrbStack.app" ]] || command -v orbstack &>/dev/null; then
83-
has_orbstack=1
84-
fi
85-
if [[ -d "/Applications/Docker.app" ]]; then
86-
has_docker_desktop=1
87-
fi
88-
89-
if [[ $has_orbstack -eq 1 && $has_docker_desktop -eq 1 ]]; then
90-
echo "OrbStack or Docker Desktop"
91-
elif [[ $has_orbstack -eq 1 ]]; then
92-
echo "OrbStack"
93-
elif [[ $has_docker_desktop -eq 1 ]]; then
94-
echo "Docker Desktop"
95-
else
96-
echo "none"
97-
fi
98-
}
79+
if [[ -f "$BOOTSTRAP_DIR/scripts/lib/runtime.sh" ]]; then
80+
# shellcheck source=scripts/lib/runtime.sh
81+
source "$BOOTSTRAP_DIR/scripts/lib/runtime.sh"
82+
else
83+
detect_installed_runtime() {
84+
local has_orbstack=0
85+
local has_docker_desktop=0
9986

100-
detect_running_runtime() {
101-
local os_name
102-
os_name=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || true)
103-
if [[ "$os_name" == *"OrbStack"* ]]; then
104-
echo "OrbStack"
105-
elif [[ "$os_name" == *"Docker Desktop"* ]]; then
106-
echo "Docker Desktop"
107-
else
108-
echo "Docker"
109-
fi
110-
}
87+
if [[ -d "/Applications/OrbStack.app" ]] || command -v orbstack &>/dev/null; then
88+
has_orbstack=1
89+
fi
90+
if [[ -d "/Applications/Docker.app" ]]; then
91+
has_docker_desktop=1
92+
fi
11193

112-
wait_for_service() {
113-
local name="$1"
114-
local url="$2"
115-
local max_attempts="${3:-45}"
116-
local attempt=0
117-
118-
while [[ $attempt -lt $max_attempts ]]; do
119-
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$url" 2>/dev/null || true)
120-
if [[ "$status" =~ ^(200|301|302|401|403)$ ]]; then
121-
echo -e " ${GREEN}OK${NC} $name is reachable"
122-
return 0
94+
if [[ $has_orbstack -eq 1 && $has_docker_desktop -eq 1 ]]; then
95+
echo "OrbStack or Docker Desktop"
96+
elif [[ $has_orbstack -eq 1 ]]; then
97+
echo "OrbStack"
98+
elif [[ $has_docker_desktop -eq 1 ]]; then
99+
echo "Docker Desktop"
100+
else
101+
echo "none"
123102
fi
124-
sleep 2
125-
attempt=$((attempt + 1))
126-
done
103+
}
104+
105+
detect_running_runtime() {
106+
local os_name
107+
os_name=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || true)
108+
if [[ "$os_name" == *"OrbStack"* ]]; then
109+
echo "OrbStack"
110+
elif [[ "$os_name" == *"Docker Desktop"* ]]; then
111+
echo "Docker Desktop"
112+
else
113+
echo "Docker"
114+
fi
115+
}
116+
117+
wait_for_service() {
118+
local name="$1"
119+
local url="$2"
120+
local max_attempts="${3:-45}"
121+
local attempt=0
122+
123+
while [[ $attempt -lt $max_attempts ]]; do
124+
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$url" 2>/dev/null || true)
125+
if [[ "$status" =~ ^(200|301|302|401|403)$ ]]; then
126+
echo -e " ${GREEN}OK${NC} $name is reachable"
127+
return 0
128+
fi
129+
sleep 2
130+
attempt=$((attempt + 1))
131+
done
127132

128-
echo -e " ${YELLOW}WARN${NC} $name is not reachable yet (continuing anyway)"
129-
return 1
130-
}
133+
echo -e " ${YELLOW}WARN${NC} $name is not reachable yet (continuing anyway)"
134+
return 1
135+
}
136+
fi
131137

132138
INSTALLED_RUNTIME=$(detect_installed_runtime)
133139

scripts/auto-heal.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
# Media Stack Auto-Healer
33
# Runs hourly via launchd. Checks VPN and container health, restarts what's broken.
44

5-
LOG_DIR="$HOME/Media/logs"
5+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
7+
# shellcheck source=scripts/lib/media-path.sh
8+
source "$SCRIPT_DIR/lib/media-path.sh"
9+
10+
MEDIA_DIR="$(resolve_media_dir "$PROJECT_DIR")"
11+
LOG_DIR="$MEDIA_DIR/logs"
612
mkdir -p "$LOG_DIR"
713
LOG="$LOG_DIR/auto-heal.log"
814

scripts/health-check.sh

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ GREEN='\033[0;32m'
77
YELLOW='\033[1;33m'
88
NC='\033[0m'
99

10+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11+
# shellcheck source=scripts/lib/runtime.sh
12+
source "$SCRIPT_DIR/scripts/lib/runtime.sh"
13+
1014
echo ""
1115
echo "=============================="
1216
echo " Media Stack Health Check"
@@ -31,41 +35,6 @@ check_service() {
3135
fi
3236
}
3337

34-
# Detect container runtime
35-
detect_installed_runtime() {
36-
local has_orbstack=0
37-
local has_docker_desktop=0
38-
39-
if [[ -d "/Applications/OrbStack.app" ]] || command -v orbstack &>/dev/null; then
40-
has_orbstack=1
41-
fi
42-
if [[ -d "/Applications/Docker.app" ]]; then
43-
has_docker_desktop=1
44-
fi
45-
46-
if [[ $has_orbstack -eq 1 && $has_docker_desktop -eq 1 ]]; then
47-
echo "OrbStack or Docker Desktop"
48-
elif [[ $has_orbstack -eq 1 ]]; then
49-
echo "OrbStack"
50-
elif [[ $has_docker_desktop -eq 1 ]]; then
51-
echo "Docker Desktop"
52-
else
53-
echo "Docker"
54-
fi
55-
}
56-
57-
detect_running_runtime() {
58-
local os_name
59-
os_name=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || true)
60-
if [[ "$os_name" == *"OrbStack"* ]]; then
61-
echo "OrbStack"
62-
elif [[ "$os_name" == *"Docker Desktop"* ]]; then
63-
echo "Docker Desktop"
64-
else
65-
echo "Docker"
66-
fi
67-
}
68-
6938
RUNTIME=$(detect_installed_runtime)
7039

7140
if docker info &>/dev/null; then

scripts/install-auto-heal.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ GREEN='\033[0;32m'
88
NC='\033[0m'
99

1010
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
11+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12+
# shellcheck source=scripts/lib/media-path.sh
13+
source "$SCRIPT_DIR/lib/media-path.sh"
14+
15+
MEDIA_DIR="$(resolve_media_dir "$PROJECT_DIR")"
1116
LAUNCH_DIR="$HOME/Library/LaunchAgents"
12-
LOG_DIR="$HOME/Media/logs/launchd"
17+
LOG_DIR="$MEDIA_DIR/logs/launchd"
1318
PLIST_NAME="com.media-stack.auto-heal"
1419
PLIST_PATH="$LAUNCH_DIR/$PLIST_NAME.plist"
1520

@@ -67,6 +72,6 @@ launchctl unload "$PLIST_PATH" 2>/dev/null || true
6772
launchctl load "$PLIST_PATH"
6873

6974
echo -e "${GREEN}Auto-heal installed.${NC} Runs every hour + on login."
70-
echo "Logs: ~/Media/logs/auto-heal.log and ~/Media/logs/launchd/"
75+
echo "Logs: $MEDIA_DIR/logs/auto-heal.log and $MEDIA_DIR/logs/launchd/"
7176
echo ""
7277
echo "To uninstall: launchctl unload $PLIST_PATH && rm $PLIST_PATH"

scripts/lib/media-path.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
# Shared MEDIA_DIR resolver.
3+
# Priority: explicit MEDIA_DIR env var -> .env MEDIA_DIR -> ~/Media
4+
5+
resolve_media_dir() {
6+
local project_dir="${1:-}"
7+
local env_file media_dir
8+
9+
if [[ -z "$project_dir" ]]; then
10+
project_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
11+
fi
12+
13+
env_file="$project_dir/.env"
14+
media_dir="${MEDIA_DIR:-}"
15+
16+
if [[ -z "$media_dir" && -f "$env_file" ]]; then
17+
media_dir="$(sed -n 's/^MEDIA_DIR=//p' "$env_file" | head -1)"
18+
fi
19+
20+
media_dir="${media_dir%\"}"
21+
media_dir="${media_dir#\"}"
22+
media_dir="${media_dir%\'}"
23+
media_dir="${media_dir#\'}"
24+
media_dir="${media_dir:-$HOME/Media}"
25+
media_dir="${media_dir/#\~/$HOME}"
26+
27+
printf '%s\n' "$media_dir"
28+
}

scripts/lib/runtime.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/bin/bash
2+
# Shared container runtime detection and readiness helpers.
3+
4+
detect_installed_runtime() {
5+
local has_orbstack=0
6+
local has_docker_desktop=0
7+
8+
if [[ -d "/Applications/OrbStack.app" ]] || command -v orbstack &>/dev/null; then
9+
has_orbstack=1
10+
fi
11+
if [[ -d "/Applications/Docker.app" ]]; then
12+
has_docker_desktop=1
13+
fi
14+
15+
if [[ $has_orbstack -eq 1 && $has_docker_desktop -eq 1 ]]; then
16+
echo "OrbStack or Docker Desktop"
17+
elif [[ $has_orbstack -eq 1 ]]; then
18+
echo "OrbStack"
19+
elif [[ $has_docker_desktop -eq 1 ]]; then
20+
echo "Docker Desktop"
21+
else
22+
echo "none"
23+
fi
24+
}
25+
26+
detect_running_runtime() {
27+
local os_name
28+
os_name=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || true)
29+
if [[ "$os_name" == *"OrbStack"* ]]; then
30+
echo "OrbStack"
31+
elif [[ "$os_name" == *"Docker Desktop"* ]]; then
32+
echo "Docker Desktop"
33+
else
34+
echo "Docker"
35+
fi
36+
}
37+
38+
wait_for_service() {
39+
local name="$1"
40+
local url="$2"
41+
local max_attempts="${3:-45}"
42+
local attempt=0
43+
local status
44+
45+
while [[ $attempt -lt $max_attempts ]]; do
46+
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$url" 2>/dev/null || true)
47+
if [[ "$status" =~ ^(200|301|302|401|403)$ ]]; then
48+
echo -e " ${GREEN}OK${NC} $name is reachable"
49+
return 0
50+
fi
51+
sleep 2
52+
attempt=$((attempt + 1))
53+
done
54+
55+
echo -e " ${YELLOW}WARN${NC} $name is not reachable yet (continuing anyway)"
56+
return 1
57+
}

0 commit comments

Comments
 (0)