captaincore/app/update
2026-03-05 18:56:48 -05:00

183 lines
7 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Runs themes, plugins and core updates on one or more sites.
#
# `captaincore update`
#
# [<site>...]
# One or more sites to update.
#
# [@<target>]
# Target groups of sites like @all @production or @staging. Use `captaincore update @production.updates-on` to target production sites marked for automatic updates.
#
# [--exclude-themes=<themes>]
# Passed onto `wp theme update --exclude=<theme-names>`
#
# [--exclude-plugins=<plugins>]
# Passed onto `wp plugin update --exclude=<theme-names>`
#
if [ ${#@} -ne 1 ]; then
echo -e "${COLOR_RED}Error:${COLOR_NORMAL} Please specify a <site>."
fi
while read config; do
if [[ "$config" == "Error:"* ]]; then
continue
fi
declare "$config"
done <<< "$(captaincore config fetch --captain-id=$CAPTAIN_ID)"
site=$1
run_command() {
# Extract environment
if [[ "$site" == *"-staging"* ]]; then
environment=staging
else
environment=production
fi
# Load site configs
IFS=$'\n'$'\r'; for line in $(captaincore site get $site --bash --captain-id=$CAPTAIN_ID); do declare "$line"; done; unset IFS
# --------------------------------------------------
# Skip if recently updated
# --------------------------------------------------
if [[ -n "$SKIP_IF_RECENT" ]]; then
update_list_file="$path/${site}_${site_id}/${environment}/update-logs/list.json"
should_skip=$(php ${CAPTAINCORE_PATH}/lib/local-scripts/check-last-run.php "$update_list_file" "$SKIP_IF_RECENT")
if [[ "$should_skip" == "true" ]]; then
echo "Skipping $site ($environment) - Updated within last $SKIP_IF_RECENT."
return
fi
fi
# --------------------------------------------------
rclone_config_file="$path/${site}_${site_id}/rclone.conf"
ftp_output=$( { rclone lsd ${environment}:$home_directory --config="$rclone_config_file" ; } 2>&1 )
ftp_search_for_wordpress=$( echo "$ftp_output" | perl -wnE'say for /wp-admin/g' )
if [[ $ftp_search_for_wordpress != "wp-admin"* ]]; then
echo "Error: WordPress not found $site-$environment"
return
fi
cd ${CAPTAINCORE_PATH}/data
echo "Quicksave before update..."
captaincore quicksave generate $site-$environment --captain-id=$CAPTAIN_ID > /dev/null 2>&1
quicksave_id_before=$( captaincore quicksave latest $site-$environment --field=hash --captain-id=$CAPTAIN_ID )
plugin_count_before=$( captaincore ssh $site-$environment --command="wp plugin list --format=csv --fields=name --skip-plugins --skip-themes | tail -n +2 | wc -l" --captain-id=$CAPTAIN_ID )
[[ "$CAPTAINCORE_DEBUG" == "true" ]] && echo "[debug] quicksave_id_before=$quicksave_id_before plugin_count_before=$plugin_count_before"
update_args="--all --format=json --provider=${provider}"
[[ -n "$updates_exclude_themes" ]] && update_args="$update_args --exclude_themes=$updates_exclude_themes"
[[ -n "$updates_exclude_plugins" ]] && update_args="$update_args --exclude_plugins=$updates_exclude_plugins"
[[ "$CAPTAINCORE_DEBUG" == "true" ]] && update_args="$update_args --debug"
echo "Updating ${site}-${environment}..."
[[ "$CAPTAINCORE_DEBUG" == "true" ]] && echo "[debug] Running remote update script with args: $update_args"
update_stderr_file=$(mktemp)
if [[ "$CAPTAINCORE_DEBUG" == "true" ]]; then
update_output=$( captaincore ssh ${site}-${environment} --script=update --captain-id=$CAPTAIN_ID -- $update_args 2> >(tee "$update_stderr_file" >&2) )
else
update_output=$( captaincore ssh ${site}-${environment} --script=update --captain-id=$CAPTAIN_ID -- $update_args 2>"$update_stderr_file" )
fi
update_exit=$?
update_errors=$(<"$update_stderr_file")
rm -f "$update_stderr_file"
# Surface errors when the remote script produced no output
if [[ -z "$update_output" && -n "$update_errors" ]]; then
# Filter to lines that look like real errors (not SSH banners or warnings)
real_errors=$( echo "$update_errors" | grep -iE "^(Error|Fatal|Warning):" | head -3 )
if [[ -n "$real_errors" ]]; then
echo " Error running updates:"
echo "$real_errors" | sed 's/^/ /'
return
fi
fi
# Parse and display update results
if command -v python3 &>/dev/null; then
formatted=$( echo "$update_output" | python3 -c "
import sys, json
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
items = json.loads(line)
if isinstance(items, list):
for item in items:
name = item.get('name', '')
old_v = item.get('old_version', '')
new_v = item.get('new_version', '')
status = item.get('status', '')
if status == 'Updated':
print(f' Updated {name} {old_v} \u2192 {new_v}')
elif status and name:
print(f' {name}: {status}')
except:
pass
" 2>/dev/null )
if [[ -n "$formatted" ]]; then
echo "$formatted"
else
echo " Everything up to date"
fi
else
echo "$update_output"
fi
echo "Quicksave after update..."
captaincore quicksave generate $site-$environment --captain-id=$CAPTAIN_ID > /dev/null 2>&1
quicksave_id_after=$( captaincore quicksave latest $site-$environment --field=hash --captain-id=$CAPTAIN_ID )
plugin_count_after=$( captaincore ssh $site-$environment --command="wp plugin list --format=csv --fields=name --skip-plugins --skip-themes | tail -n +2 | wc -l" --captain-id=$CAPTAIN_ID )
[[ "$CAPTAINCORE_DEBUG" == "true" ]] && echo "[debug] quicksave_id_after=$quicksave_id_after plugin_count_after=$plugin_count_after"
if [[ $plugin_count_before != $plugin_count_after ]]; then
mismatch_subject="Plugin update mismatch ($plugin_count_before:$plugin_count_after) on $home_url"
mismatch_body="Updating plugins on $home_url ($site-$environment) resulted in a mismatch count.<br /><br />Before updating there were $plugin_count_before plugins.<br />After updating there were $plugin_count_after plugins."
mismatch_content_json=$( echo "$mismatch_body" | php -r 'echo json_encode(file_get_contents("php://stdin"));' )
if [[ $captaincore_dev == true ]]; then
curl_argument="-k"
fi
curl ${curl_argument} --silent --request POST "$captaincore_api" --header "Content-Type: application/json" --data @- << EOF
{
"command": "monitor-notify",
"token": "$token",
"data": {
"email": "$captaincore_admin_email",
"subject": "$mismatch_subject",
"content": $mismatch_content_json
}
}
EOF
fi
if [[ $quicksave_id_before != $quicksave_id_after ]]; then
captaincore update-log generate $site-$environment $quicksave_id_before $quicksave_id_after --captain-id=$CAPTAIN_ID > /dev/null
captaincore update-log list-generate $site-$environment --captain-id=$CAPTAIN_ID > /dev/null
echo "Update log saved"
else
# Touch list.json so --skip-if-recent knows this site was checked
update_list_file="$path/${site}_${site_id}/${environment}/update-logs/list.json"
mkdir -p "$(dirname "$update_list_file")"
[[ ! -f "$update_list_file" ]] && echo "[]" > "$update_list_file"
touch "$update_list_file"
fi
}
run_command