Veruse / fix-wp-permissions.sh

Last active 3 weeks ago

Like 0

bash <(curl -fsSL https://gist.veru.se/Veruse/fix-wp-permissions/raw/HEAD/fix-wp-permissions.sh)

Revision 20a1a21578df3b42757ebe78b15955007ddd8b3e

fix-wp-permissions.sh Raw
1#!/usr/bin/env bash
2#
3# fix-wp-permissions.sh — reset file and folder permissions on a WordPress install.
4#
5# Defaults (WordPress-recommended):
6# directories 755
7# files 644
8# wp-config.php 640 (use --strict for 600)
9# .htaccess 644
10# wp-content/uploads writable by the web server group (775 dirs / 664 files) with --group-write
11#
12# Run from a URL (pass options after "bash -s --"):
13# curl -fsSL https://gist.veru.se/Veruse/fix-wp-permissions | bash -s -- --dry-run
14# curl -fsSL https://gist.veru.se/Veruse/fix-wp-permissions | sudo bash -s -- -p /var/www/example.com -o deploy -g www-data --group-write
15#
16# Or locally:
17# ./fix-wp-permissions.sh [-p path] [-o owner] [-g group] [--group-write] [--strict] [--dry-run]
18#
19# With no -p, the current directory is used.
20
21# Everything runs inside main(), called on the last line, so a partially
22# downloaded script never executes.
23main() {
24set -euo pipefail
25
26WP_PATH="$PWD"
27OWNER=""
28GROUP=""
29GROUP_WRITE=false
30STRICT=false
31DRY_RUN=false
32
33usage() {
34 cat <<'USAGE'
35Usage: fix-wp-permissions.sh [options]
36
37 -p, --path DIR WordPress root (default: current directory)
38 -o, --owner USER chown files to USER (needs root)
39 -g, --group GROUP chown files to GROUP, e.g. www-data (needs root)
40 --group-write make wp-content/uploads writable by the group (775/664)
41 --strict wp-config.php 600 instead of 640
42 -n, --dry-run show what would change, change nothing
43 -h, --help show this help
44
45From a URL:
46 curl -fsSL https://gist.veru.se/Veruse/fix-wp-permissions | bash -s -- --dry-run
47USAGE
48 exit "${1:-0}"
49}
50
51while [[ $# -gt 0 ]]; do
52 case "$1" in
53 -p|--path) WP_PATH="${2:-}"; shift 2 ;;
54 -o|--owner) OWNER="${2:-}"; shift 2 ;;
55 -g|--group) GROUP="${2:-}"; shift 2 ;;
56 --group-write) GROUP_WRITE=true; shift ;;
57 --strict) STRICT=true; shift ;;
58 -n|--dry-run) DRY_RUN=true; shift ;;
59 -h|--help) usage 0 ;;
60 *) echo "Unknown option: $1" >&2; usage 1 ;;
61 esac
62done
63
64# --- Sanity checks ---------------------------------------------------------
65
66[[ -z "$WP_PATH" ]] && { echo "Error: --path is empty." >&2; usage 1; }
67WP_PATH="$(cd "$WP_PATH" 2>/dev/null && pwd)" || { echo "Error: path not found." >&2; exit 1; }
68
69if [[ "$WP_PATH" == "/" || "$WP_PATH" == "$HOME" && ! -d "$WP_PATH/wp-includes" ]]; then
70 echo "Error: refusing to run on '$WP_PATH'." >&2
71 exit 1
72fi
73
74if [[ ! -d "$WP_PATH/wp-includes" || ! -d "$WP_PATH/wp-admin" ]]; then
75 echo "Error: '$WP_PATH' doesn't look like a WordPress root (no wp-includes/ or wp-admin/)." >&2
76 exit 1
77fi
78
79# wp-config.php can live in the WP root or one level above it.
80WP_CONFIG=""
81if [[ -f "$WP_PATH/wp-config.php" ]]; then WP_CONFIG="$WP_PATH/wp-config.php"
82elif [[ -f "$WP_PATH/../wp-config.php" ]]; then WP_CONFIG="$(cd "$WP_PATH/.." && pwd)/wp-config.php"
83fi
84
85if [[ -n "$OWNER" || -n "$GROUP" ]] && [[ $EUID -ne 0 ]]; then
86 echo "Error: changing ownership needs root. Re-run with sudo, or drop -o/-g." >&2
87 exit 1
88fi
89[[ -n "$OWNER" ]] && ! id -u "$OWNER" &>/dev/null && { echo "Error: user '$OWNER' doesn't exist." >&2; exit 1; }
90[[ -n "$GROUP" ]] && ! getent group "$GROUP" &>/dev/null && { echo "Error: group '$GROUP' doesn't exist." >&2; exit 1; }
91
92run() {
93 if $DRY_RUN; then echo "[dry-run] $*"; else "$@"; fi
94}
95
96echo "WordPress root : $WP_PATH"
97echo "wp-config.php : ${WP_CONFIG:-not found}"
98echo "Owner:group : ${OWNER:-(unchanged)}:${GROUP:-(unchanged)}"
99echo "Uploads writable by group: $GROUP_WRITE"
100$DRY_RUN && echo "DRY RUN — nothing will be changed."
101echo
102
103# --- Ownership -------------------------------------------------------------
104
105if [[ -n "$OWNER" || -n "$GROUP" ]]; then
106 echo "Setting ownership..."
107 run chown -R "${OWNER}${GROUP:+:$GROUP}" "$WP_PATH"
108 [[ -n "$WP_CONFIG" && "$WP_CONFIG" != "$WP_PATH/wp-config.php" ]] && \
109 run chown "${OWNER}${GROUP:+:$GROUP}" "$WP_CONFIG"
110fi
111
112# --- Base permissions ------------------------------------------------------
113# -P: don't follow symlinks; chmod on symlinks is skipped by -type d / -type f.
114
115echo "Setting directories to 755..."
116run find -P "$WP_PATH" -type d -exec chmod 755 {} +
117
118echo "Setting files to 644..."
119run find -P "$WP_PATH" -type f -exec chmod 644 {} +
120
121# --- Uploads ---------------------------------------------------------------
122
123UPLOADS="$WP_PATH/wp-content/uploads"
124if $GROUP_WRITE && [[ -d "$UPLOADS" ]]; then
125 echo "Making uploads group-writable (775/664)..."
126 run find -P "$UPLOADS" -type d -exec chmod 775 {} +
127 run find -P "$UPLOADS" -type f -exec chmod 664 {} +
128 # setgid so new uploads inherit the group
129 run find -P "$UPLOADS" -type d -exec chmod g+s {} +
130fi
131
132# --- Sensitive files -------------------------------------------------------
133
134if [[ -n "$WP_CONFIG" ]]; then
135 if $STRICT; then
136 echo "Locking wp-config.php to 600..."
137 run chmod 600 "$WP_CONFIG"
138 else
139 echo "Locking wp-config.php to 640..."
140 run chmod 640 "$WP_CONFIG"
141 fi
142fi
143
144[[ -f "$WP_PATH/.htaccess" ]] && run chmod 644 "$WP_PATH/.htaccess"
145
146# Any scripts/binaries that genuinely need +x (e.g. in vendor/bin) can be
147# re-added here, e.g.:
148# run find "$WP_PATH/wp-content" -path '*/vendor/bin/*' -type f -exec chmod 755 {} +
149
150echo
151echo "Done."
152$DRY_RUN || echo "Tip: load the site and test uploads, plugin updates and the media library."
153}
154
155main "$@"