Skip to content

Commit 282ee55

Browse files
committed
fix: harden permission checks and improve audit coverage
1 parent 6e192fc commit 282ee55

4 files changed

Lines changed: 89 additions & 4 deletions

File tree

.github/workflows/validate.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,26 @@ jobs:
1414
- name: Validate shell scripts
1515
run: |
1616
bash -n fix-permissions.sh
17+
18+
- name: Smoke test audit mode
19+
run: |
20+
TMP_MEDIA="$(mktemp -d)"
21+
mkdir -p "$TMP_MEDIA/config/sonarr" "$TMP_MEDIA/Downloads" "$TMP_MEDIA/TV Shows"
22+
cat > "$TMP_MEDIA/docker-compose.yml" <<'EOF'
23+
services:
24+
sonarr:
25+
image: lscr.io/linuxserver/sonarr:latest
26+
environment:
27+
- PUID=${PUID}
28+
- PGID=${PGID}
29+
volumes:
30+
- ${MEDIA_DIR}/config/sonarr:/config
31+
- ${MEDIA_DIR}/TV Shows:/tv
32+
- ${MEDIA_DIR}/Downloads:/downloads
33+
EOF
34+
cat > "$TMP_MEDIA/.env" <<EOF
35+
PUID=$(id -u)
36+
PGID=$(id -g)
37+
MEDIA_DIR=$TMP_MEDIA
38+
EOF
39+
bash fix-permissions.sh --path "$TMP_MEDIA"

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,20 @@ bash fix-permissions.sh --fix --allow-outside-media-dir
8282
<img src="demo.gif" alt="Permission audit demo" width="700" />
8383
</details>
8484

85+
<details>
86+
<summary>Fix mode demo</summary>
87+
<br>
88+
<img src="restore-demo.gif" alt="Permission fix demo" width="700" />
89+
</details>
90+
8591
## Fix Mode
8692

8793
Run with `--fix` to automatically resolve permission issues:
8894

8995
- Runs `chown -R` on directories with wrong ownership
9096
- Reports what was changed
9197
- Protects paths outside `--path` by default (use `--allow-outside-media-dir` to override)
98+
- Exits non-zero only if unresolved failures remain after fixes
9299

93100
The script never modifies docker-compose.yml or .env. It only fixes file ownership on disk.
94101

fix-permissions.sh

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,51 @@ ENV_FILE="$MEDIA_DIR/.env"
182182
ENV_PUID=""
183183
ENV_PGID=""
184184

185+
env_value() {
186+
local key="$1"
187+
local file="${2:-}"
188+
local value=""
189+
190+
if [[ -n "$file" ]] && [[ -f "$file" ]]; then
191+
value=$(sed -n "s/^${key}=//p" "$file" | head -1 | tr -d '[:space:]')
192+
fi
193+
if [[ -z "$value" ]]; then
194+
eval "value=\"\${$key:-}\""
195+
fi
196+
printf '%s\n' "$value"
197+
}
198+
199+
expand_host_path() {
200+
local path="$1"
201+
local key value
202+
203+
path="${path//\\\$HOME/$HOME}"
204+
path="${path//\$HOME/$HOME}"
205+
path="${path/#\~/$HOME}"
206+
207+
while [[ "$path" =~ \$\{([A-Za-z_][A-Za-z0-9_]*)\} ]]; do
208+
key="${BASH_REMATCH[1]}"
209+
if [[ "$key" == "MEDIA_DIR" ]]; then
210+
value="$MEDIA_DIR"
211+
else
212+
value="$(env_value "$key" "$ENV_FILE")"
213+
fi
214+
215+
if [[ -z "$value" ]]; then
216+
break
217+
fi
218+
path="${path//\$\{$key\}/$value}"
219+
done
220+
221+
printf '%s\n' "$path"
222+
}
223+
185224
if [[ -f "$ENV_FILE" ]]; then
186225
pass ".env file found"
187226

188227
# Read PUID from .env
189228
if grep -q "^PUID=" "$ENV_FILE" 2>/dev/null; then
190-
ENV_PUID=$(grep "^PUID=" "$ENV_FILE" | head -1 | cut -d'=' -f2 | tr -d '[:space:]')
229+
ENV_PUID=$(env_value "PUID" "$ENV_FILE")
191230
if [[ "$ENV_PUID" == "$EXPECTED_PUID" ]]; then
192231
pass ".env PUID ($ENV_PUID) matches current user"
193232
else
@@ -199,7 +238,7 @@ if [[ -f "$ENV_FILE" ]]; then
199238

200239
# Read PGID from .env
201240
if grep -q "^PGID=" "$ENV_FILE" 2>/dev/null; then
202-
ENV_PGID=$(grep "^PGID=" "$ENV_FILE" | head -1 | cut -d'=' -f2 | tr -d '[:space:]')
241+
ENV_PGID=$(env_value "PGID" "$ENV_FILE")
203242
if [[ "$ENV_PGID" == "$EXPECTED_PGID" ]]; then
204243
pass ".env PGID ($ENV_PGID) matches current group"
205244
else
@@ -309,15 +348,30 @@ echo ""
309348
# Check common media stack directories
310349
DIRS_TO_CHECK=(
311350
"config"
351+
"Config"
312352
"downloads"
353+
"Downloads"
313354
"movies"
355+
"Movies"
314356
"tv"
357+
"TV"
358+
"TV Shows"
315359
"music"
360+
"Music"
316361
"books"
362+
"Books"
317363
"media"
364+
"Media"
318365
"torrents"
366+
"Torrents"
319367
"usenet"
368+
"Usenet"
320369
"transcode"
370+
"Transcode"
371+
"logs"
372+
"Logs"
373+
"state"
374+
"State"
321375
"backup"
322376
"backups"
323377
)
@@ -346,6 +400,7 @@ for dir in "${DIRS_TO_CHECK[@]}"; do
346400
echo -e "${CYAN}FIX${NC} Running: chown -R $EXPECTED_PUID:$EXPECTED_PGID $FULL_PATH"
347401
sudo chown -R "$EXPECTED_PUID:$EXPECTED_PGID" "$FULL_PATH"
348402
pass "$dir/ ownership fixed to $EXPECTED_PUID:$EXPECTED_PGID"
403+
FAIL_COUNT=$((FAIL_COUNT - 1))
349404
fi
350405
fi
351406
fi
@@ -361,8 +416,7 @@ if [[ -n "$COMPOSE_FILE" ]]; then
361416
while IFS= read -r line; do
362417
if echo "$line" | grep -qE '^\s+- .+:.+'; then
363418
HOST_PATH=$(echo "$line" | sed 's/^[[:space:]]*- //' | cut -d':' -f1 | tr -d '"'"'"'')
364-
# Expand variables
365-
HOST_PATH=$(echo "$HOST_PATH" | sed "s|\\\${MEDIA_DIR}|$MEDIA_DIR|g" | sed "s|\${MEDIA_DIR}|$MEDIA_DIR|g")
419+
HOST_PATH=$(expand_host_path "$HOST_PATH")
366420
# Skip if it's a named volume (no slash)
367421
if [[ "$HOST_PATH" == /* ]] && [[ -d "$HOST_PATH" ]]; then
368422
DIR_UID=$(stat -f "%u" "$HOST_PATH")
@@ -384,6 +438,7 @@ if [[ -n "$COMPOSE_FILE" ]]; then
384438
echo -e "${CYAN}FIX${NC} Running: chown -R $EXPECTED_PUID:$EXPECTED_PGID $HOST_PATH"
385439
sudo chown -R "$EXPECTED_PUID:$EXPECTED_PGID" "$HOST_PATH"
386440
pass "Volume $SHORT_PATH ownership fixed"
441+
FAIL_COUNT=$((FAIL_COUNT - 1))
387442
fi
388443
fi
389444
fi

restore-demo.gif

17.2 KB
Loading

0 commit comments

Comments
 (0)