I have a request about UUPD and whether it would support the use of Google Drive as a source for backups. While I saw no reason it wouldn’t, after all, it’s entirely agnostic, why wouldn’t Google Drive be supported as long as it’s presenting a downloadable Zip and a JSON file that can be read? The rest is pretty much irrelevant.
So I thought I would test this. The good news is it worked, but there are some items you need to consider with your JSON file for this to work, and as I had to work them out to test it, I wanted to publish an article with the specifics of them and my revised deployment script to allow for Google Drive deployment.
Now my deployment script assumes you’re using a native way to migrate local folders to Google Drive and keep the same file names/references, e.g., the Google Drive application or SyncBack Pro.
The Google Specifics
Firstly, our server URL is slightly different to standard Google Drive links in the example below 1rBno_y2EjgBgnJwtXeF9pNsl0In-KvaR is the Drive file reference
https://drive.google.com/uc?export=download&id=1rBno_y2EjgBgnJwtXeF9pNsl0In-KvaRPHPThis is what goes in our plugin as the server link and returns the JSON we need for updates. An example JSON file is below, which also has a couple of similar links for the zip file and also for thumbnail photos, which are also stored in Google Drive for the icons and banners. Now, in my testing, I wasn’t impressed with the thumbnail quality for banner images.
{
"slug": "notify-for-suremail",
"name": "Notify for SureMail",
"version": "0.9.8",
"author": "ReallyUsefulPlugins.com",
"author_homepage": "https://reallyusefulplugins.com",
"requires_php": "8.0",
"requires": "6.5",
"tested": "6.8.3",
"sections": {
"description": "Sends Pushover, Discord, Generic Webhook and Slack notifications when emails are blocked, fail, or succeed.",
"installation": "1. Upload the `notify-for-suremail` folder to the `/wp-content/plugins/` directory.<br>\r\n2. Go to a Guttenberg page you will now have a new block.<br>\r\n3. use the configurator to modify the use bubbles embed",
"frequently_asked_questions": "",
"changelog": "<h4>0.9.8 09 November 2025</h4><ul><li>New: Update Deploy Script</li></ul><h4>0.9.7 22 August 2025</h4><ul><li>New: First Updater Test (Update)</li><li>New: Notify for Suremail</li></ul><h4>0.9.6 22 August 2025</h4><ul><li>New: Initial Release</li><li>New: First Updater Test</li></ul>"
},
"last_updated": "2025-11-09 00:53:38",
"download_url": "https://drive.google.com/uc?export=download&id=1khTJmtyjB0YLEpnKC914abAX9i2cUJWk",
"checksum": {
"algo": "sha256",
"hash": "88ac4cb4fcd513aeec4b66007a1affec1fe1e996b9755c89b3bcc4c51f90c72c"
},
"banners": {
"low": "https://drive.google.com/thumbnail?id=18QRqW3FzJ4yPXVnqbCjVuWnUN9SvDkRF&sz=w772",
"high": "https://drive.google.com/thumbnail?id=18QRqW3FzJ4yPXVnqbCjVuWnUN9SvDkRF&sz=w1544"
},
"icons": {
"1x": "https://drive.google.com/thumbnail?id=12DVmsA78CBVIkDGXkSMuFpg6qW6WaTSN&sz=w128",
"2x": "https://drive.google.com/thumbnail?id=1megB0O3LbaTfGKeh-RtdDUAOJsBgMCQi&sz=w256"
}
}
JSONThis is, in effect, all you need for your typical hosting of a WordPress plugin and updates on Google Drive using UUPD. Follow the formatting and it will work fine, not all references are actually needed i.e drive_file_id and package are not actually used we use download URL
Updated Deploy Script
At the same time, I had a play with my deploy script, which can now handle Drive, Local (Syncback) and GitHub deployment, and the files for these are below, which include a new locally hosted PHP file, a sh file and a new cfg config file.
I used this to test auto deployment, and it worked great, and can be modified for your own environment.
Config – CFG example
; =========================
; Project basics
; =========================
PLUGIN_NAME=Notify for SureMail
PLUGIN_TAGS=SureMail, Discord, Pushover, Slack, Webhook
PLUGIN_SLUG=notify-for-suremail
; Your GitHub repo (owner/repo)
GITHUB_REPO=stingray82/notify-for-suremail
; Output ZIP name
ZIP_NAME=notify-for-suremail.zip
; Legacy single target (kept for compatibility). If set, it will be used
; when DEPLOY_TARGETS is empty. Options: github, local, drive
DEPLOY_TARGET=
; NEW multi-target list (comma-separated): local,github,drive
; Example: DEPLOY_TARGETS=github,drive
; If empty, we fall back to DEPLOY_TARGET (above) or "github".
DEPLOY_TARGETS=github,drive
; Files used to build readme & release body
CHANGELOG_FILE=C:\Users\Nathan\Git\rup-changelogs\notify-for-suremail.txt
STATIC_FILE=static.txt
; =========================
; LOCAL target (private folder / NAS / staging)
; Only used if "local" is in DEPLOY_TARGETS (or DEPLOY_TARGET=local)
; =========================
; Legacy DEST_DIR kept for compatibility. If LOCAL_DEST_DIR is empty, the script uses DEST_DIR.
DEST_DIR=
LOCAL_DEST_DIR=
; =========================
; GITHUB target
; Only used if "github" is in DEPLOY_TARGETS (or DEPLOY_TARGET=github)
; =========================
; Optional: read token from this file if env var GITHUB_TOKEN isn't set
TOKEN_FILE=C:/Ignore By Avast/0. PATHED Items/Plugins/deployscripts/github_token.txt
; Optional: tag prefix (final tag becomes <prefix>v<version>; leave empty or set to "v")
GITHUB_TAG_PREFIX=
; Optional: set to 1 to mark the release as a prerelease
GITHUB_RELEASE_PRERELEASE=0
; =========================
; DRIVE (UUPD) target
; Only used if "drive" is in DEPLOY_TARGETS (or DEPLOY_TARGET=drive)
; =========================
; Local path to your Google Drive *synced* folder for this plugin
GDRIVE_SYNC_DIR=G:/My Drive/trial
;php Script for GDRIVE
GDRIVE_JSON_SCRIPT=C:/Ignore By Avast/0. PATHED Items/Plugins/deployscripts/gdrivejson.php
; The EXACT two filenames inside that folder (keep constant for stable file IDs)
GDRIVE_ZIP_NAME=notify-for-suremail.zip
GDRIVE_MANIFEST_NAME=notify-for-suremail-update-info.json
; One-time: get these from Drive web UI:
; Right-click file -> Get link -> open the /file/d/<ID>/view URL -> copy the <ID>
; IMPORTANT: Keep filenames fixed and overwrite content so IDs (and URLs) never change.
GDRIVE_ZIP_FILE_ID=1khTJmtyjB0YLEpnKC914abAX9i2cUJWk
GDRIVE_MANIFEST_FILE_ID=1rBno_y2EjgBgnJwtXeF9pNsl0In-KvaR
; ======= Drive bootstrap options =======
; Allow the script to proceed and write the manifest even if the ZIP ID is missing.
; Default 0 = safer (won’t publish a broken manifest). Set 1 for a soft bootstrap.
ALLOW_EMPTY_DRIVE_ID=0
; If 1, the script opens the synced folder in Explorer on first-run/missing-IDs to help you right-click -> View in web.
AUTO_OPEN_EXPLORER=1
; You may paste either a raw file ID or a full Google Drive URL here. The script extracts the ID automatically.
; e.g. GDRIVE_ZIP_FILE_ID=https://drive.google.com/file/d/1AbCdEF.../view?usp=sharing
; GDRIVE_MANIFEST_FILE_ID=1ZyXwVu...
; ======== Rich UUPD metadata (Drive) ========
; Everything here is optional.auto-fill what we can from plugin headers (slug/name/version/requires/tested/requires_php).
; Override plugin display name in manifest (defaults to PLUGIN_NAME)
UUPD_NAME=Notify for SureMail
; Author info
UUPD_AUTHOR=ReallyUsefulPlugins.com
UUPD_AUTHOR_HOMEPAGE=https://reallyusefulplugins.com
; Explicit WP and PHP requirements (fallback to plugin headers if blank)
UUPD_REQUIRES=
UUPD_TESTED=
UUPD_REQUIRES_PHP=
; Sections: provide small HTML/text files; we’ll read them if paths exist.
UUPD_SECTION_DESCRIPTION_FILE=uupd/section-description.html
UUPD_SECTION_INSTALLATION_FILE=uupd/section-installation.html
UUPD_SECTION_FAQ_FILE=uupd/section-faq.html
; If you already keep an HTML changelog, point to it. Otherwise we can keep using your text changelog separately.
UUPD_SECTION_CHANGELOG_HTML_FILE=uupd/section-changelog.html
; “last_updated” will be generated automatically with local time.
; You can override it (format: YYYY-MM-DD HH:MM:SS) by setting:
; UUPD_LAST_UPDATED=2025-11-08 23:07:30
; Download URL to show in the metadata (purely informational for UUPD).
; If blank and you have GITHUB_REPO set, we’ll default to your GitHub “latest” asset.
UUPD_DOWNLOAD_URL=
; Banners / icons (use fully-qualified URLs). If blank and GITHUB_REPO is set,
; we’ll auto-point at your repo’s /uupd/ assets on raw.githubusercontent.
; https://drive.google.com/thumbnail?id=1megB0O3LbaTfGKeh-RtdDUAOJsBgMCQi&sz=w256 # ID = ID W=Width
UUPD_BANNER_LOW=
UUPD_BANNER_HIGH=
UUPD_ICON_1X=https://drive.google.com/thumbnail?id=12DVmsA78CBVIkDGXkSMuFpg6qW6WaTSN&sz=w128
UUPD_ICON_2X=https://drive.google.com/thumbnail?id=1megB0O3LbaTfGKeh-RtdDUAOJsBgMCQi&sz=w256
; example link for UUPD
; https://drive.google.com/uc?export=download&id=1rBno_y2EjgBgnJwtXeF9pNsl0In-KvaR
JSONSH
#!/usr/bin/env bash
set -uo pipefail
# Ensure TMPDIR exists (for mktemp etc.)
: "${TMPDIR:=$(mktemp -d)}"
# =====================================================
# PATH SETUP
# =====================================================
script_dir="$(cd -- "$(dirname -- "$0")" && pwd -P)"
config_file="$script_dir/deploy-test.cfg"
# =====================================================
# LOAD CONFIG (handles #/; comments, blank lines)
# =====================================================
if [[ ! -f "$config_file" ]]; then
echo "[ERROR] Config file not found: $config_file"
exit 1
fi
# Clear (so old env doesn’t leak)
unset PLUGIN_NAME PLUGIN_TAGS PLUGIN_SLUG HEADER_SCRIPT CHANGELOG_FILE STATIC_FILE ZIP_NAME GENERATOR_SCRIPT
unset GITHUB_REPO TOKEN_FILE GITHUB_TOKEN DEST_DIR DEPLOY_TARGET DEPLOY_TARGETS
unset LOCAL_DEST_DIR GITHUB_OWNER GITHUB_TAG_PREFIX GITHUB_RELEASE_PRERELEASE
unset GDRIVE_SYNC_DIR GDRIVE_ZIP_NAME GDRIVE_MANIFEST_NAME GDRIVE_ZIP_FILE_ID GDRIVE_MANIFEST_FILE_ID
# Parse KEY=VALUE (ignore comments and blanks)
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line//$'\r'/}"
[[ -z "$line" || "${line:0:1}" == "#" || "${line:0:1}" == ";" ]] && continue
key="${line%%=*}"
val="${line#*=}"
key="$(echo "$key" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
val="$(echo "$val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
eval "$key=\"\$val\""
done < "$config_file"
# =====================================================
# CONSTANTS / SHARED TOOLS (same defaults as before)
# =====================================================
HEADER_SCRIPT="${HEADER_SCRIPT:-C:/Ignore By Avast/0. PATHED Items/Plugins/deployscripts/myplugin_headers.php}"
TOKEN_FILE="${TOKEN_FILE:-C:/Ignore By Avast/0. PATHED Items/Plugins/deployscripts/github_token.txt}"
GENERATOR_SCRIPT="${GENERATOR_SCRIPT:-C:/Ignore By Avast/0. PATHED Items/Plugins/deployscripts/generate_index.php}"
GDRIVE_JSON_SCRIPT="${GDRIVE_JSON_SCRIPT:-C:/Ignore By Avast/0. PATHED Items/Plugins/deployscripts/gdrivejson.php}"
# =====================================================
# DEFAULTS / VALIDATION
# =====================================================
if [[ -z "${PLUGIN_SLUG:-}" ]]; then
echo "[ERROR] PLUGIN_SLUG is not defined in deploy.cfg"
exit 1
fi
if [[ -z "${GITHUB_REPO:-}" ]]; then
echo "[WARN] GITHUB_REPO not defined. GitHub target will be unavailable unless set in deploy.cfg."
fi
ZIP_NAME="${ZIP_NAME:-$PLUGIN_SLUG.zip}"
CHANGELOG_FILE="${CHANGELOG_FILE:-changelog.txt}"
STATIC_FILE="${STATIC_FILE:-static.txt}"
PLUGIN_NAME="${PLUGIN_NAME:-$PLUGIN_SLUG}"
PLUGIN_TAGS="${PLUGIN_TAGS:-}"
# Back-compat: if old single-target var is present, use it; otherwise DEPLOY_TARGETS
if [[ -n "${DEPLOY_TARGET:-}" && -z "${DEPLOY_TARGETS:-}" ]]; then
DEPLOY_TARGETS="$DEPLOY_TARGET"
fi
DEPLOY_TARGETS="${DEPLOY_TARGETS:-github}"
# Derived paths
repo_root="$script_dir"
plugin_dir="$script_dir/$PLUGIN_SLUG"
plugin_file="$plugin_dir/$PLUGIN_SLUG.php"
readme_file="$plugin_dir/readme.txt"
temp_readme="$plugin_dir/readme_temp.txt"
static_subfolder="$repo_root/uupd"
# =====================================================
# VERIFY REQUIRED FILES
# =====================================================
[[ -f "$plugin_file" ]] || { echo "[ERROR] Plugin file not found: $plugin_file"; exit 1; }
[[ -f "$CHANGELOG_FILE" ]] || { echo "[ERROR] Changelog file not found: $CHANGELOG_FILE"; exit 1; }
[[ -f "$STATIC_FILE" ]] || { echo "[ERROR] Static readme file not found: $STATIC_FILE"; exit 1; }
# =====================================================
# RUN HEADER SCRIPT (updates plugin headers if needed)
# =====================================================
php "$HEADER_SCRIPT" "$plugin_file"
# =====================================================
# EXTRACT METADATA FROM HEADERS
# (robust to either "Header:" or "* Header:" styles)
# =====================================================
requires_at_least="$(
grep -m1 -E '^(Requires at least:|[[:space:]]*\*[[:space:]]*Requires at least:)' "$plugin_file" \
| sed -E 's/.*Requires at least:[[:space:]]*//' || true
)"
tested_up_to="$(
grep -m1 -E '^(Tested up to:|[[:space:]]*\*[[:space:]]*Tested up to:)' "$plugin_file" \
| sed -E 's/.*Tested up to:[[:space:]]*//' || true
)"
requires_php="$(
grep -m1 -E '^(Requires PHP:|[[:space:]]*\*[[:space:]]*Requires PHP:)' "$plugin_file" \
| sed -E 's/.*Requires PHP:[[:space:]]*//' || true
)"
version="$(
grep -m1 -E '^(Version:|[[:space:]]*\*[[:space:]]*Version)' "$plugin_file" \
| sed -E 's/.*Version[: ]+[[:space:]]*//; s/\r//; s/[[:space:]]+$//' || true
)"
if [[ -z "$version" ]]; then
version_line="$(grep -m1 -E '^[[:space:]]*\*[[:space:]]*Version' "$plugin_file" || true)"
version="$(sed -E 's/.*Version[: ]+[[:space:]]*//; s/\r//; s/[[:space:]]+$//' <<< "$version_line")"
fi
[[ -n "$version" ]] || { echo "[ERROR] Could not extract Version from $plugin_file"; exit 1; }
# =====================================================
# GENERATE STATIC index.json FOR GITHUB DELIVERY
# (used by your UUPD GitHub path)
# =====================================================
echo "[INFO] Generating index.json for GitHub delivery..."
github_user="${GITHUB_REPO%%/*}"
repo_name="${GITHUB_REPO#*/}"
cdn_path="https://raw.githubusercontent.com/$github_user/$repo_name/main/uupd"
mkdir -p "$static_subfolder"
php "$GENERATOR_SCRIPT" \
"$plugin_file" \
"$CHANGELOG_FILE" \
"$static_subfolder" \
"$github_user" \
"$cdn_path" \
"$repo_name" \
"$repo_name" \
"$STATIC_FILE" \
"$ZIP_NAME"
if [[ -f "$static_subfolder/index.json" ]]; then
echo "[OK] index.json generated: $static_subfolder/index.json"
else
echo "[WARN] Failed to generate index.json (GitHub UUPD path will lack index.json)"
fi
# =====================================================
# CREATE README.TXT
# =====================================================
{
echo "=== $PLUGIN_NAME ==="
echo "Contributors: reallyusefulplugins"
echo "Donate link: https://reallyusefulplugins.com/donate"
echo "Tags: $PLUGIN_TAGS"
echo "Requires at least: $requires_at_least"
echo "Tested up to: $tested_up_to"
echo "Stable tag: $version"
echo "Requires PHP: $requires_php"
echo "License: GPL-2.0-or-later"
echo "License URI: https://www.gnu.org/licenses/gpl-2.0.html"
echo
} > "$temp_readme"
cat "$STATIC_FILE" >> "$temp_readme"
echo >> "$temp_readme"
echo "== Changelog ==" >> "$temp_readme"
cat "$CHANGELOG_FILE" >> "$temp_readme"
if [[ -f "$readme_file" ]]; then
cp -f "$readme_file" "$readme_file.bak"
fi
mv -f "$temp_readme" "$readme_file"
# =====================================================
# GIT COMMIT AND PUSH CHANGES
# =====================================================
pushd "$plugin_dir" >/dev/null
git add -A
if ! git diff --cached --quiet; then
git commit -m "Version $version Release"
git push origin main
echo "[OK] Git commit and push complete."
else
echo "[INFO] No changes to commit."
fi
popd >/dev/null
# =====================================================
# ZIP PLUGIN FOLDER
# =====================================================
sevenzip_win="/c/Program Files/7-Zip/7z.exe"
zip_file="$script_dir/$ZIP_NAME"
if [[ -x "$sevenzip_win" ]]; then
pushd "$script_dir" >/dev/null
"$sevenzip_win" a -tzip "$zip_file" "$PLUGIN_SLUG" >/dev/null
popd >/dev/null
else
# Fallback to tar -a (creates zip if extension is .zip)
pushd "$script_dir" >/dev/null
tar -a -c -f "$zip_file" "$PLUGIN_SLUG"
popd >/dev/null
fi
if [[ -f "$zip_file" ]]; then
echo "[OK] Zipped to: $zip_file"
else
echo "[ERROR] Failed to create archive."
exit 1
fi
# =====================================================
# DEPLOY HELPERS
# =====================================================
die(){ echo "[ERROR] $*" >&2; exit 1; }
info(){ echo "[INFO] $*"; }
ok(){ echo "[OK] $*"; }
# ---------- Deploy: LOCAL (private folder) ----------
deploy_local() {
: "${LOCAL_DEST_DIR:?LOCAL_DEST_DIR missing in deploy.cfg}"
mkdir -p "$LOCAL_DEST_DIR" || die "Cannot create LOCAL_DEST_DIR"
cp -f "$zip_file" "$LOCAL_DEST_DIR/" || die "Copy to LOCAL_DEST_DIR failed"
ok "Local deploy -> $LOCAL_DEST_DIR/$(basename "$zip_file")"
}
get_sha256() {
local path="$1"
# 1) Try PHP (works great in Git Bash shells)
if command -v php >/dev/null 2>&1; then
local out
out="$(php -r "echo hash_file('sha256', '$path');" 2>/dev/null || true)"
if [[ -n "$out" ]]; then echo "$out"; return 0; fi
fi
# 2) Try certutil (present on Windows)
if command -v certutil >/dev/null 2>&1; then
# The hash is printed on line 2; strip spaces/CR
local out
out="$(certutil -hashfile "$path" SHA256 2>/dev/null | sed -n '2p' | tr -d ' \r' || true)"
if [[ -n "$out" ]]; then echo "$out"; return 0; fi
fi
# 3) Try PowerShell Get-FileHash
if command -v powershell >/dev/null 2>&1; then
local out
out="$(powershell -NoProfile -Command "(Get-FileHash -LiteralPath '$path' -Algorithm SHA256).Hash" 2>/dev/null | tr -d '\r' || true)"
if [[ -n "$out" ]]; then echo "$out"; return 0; fi
fi
return 1
}
# ---------- Deploy: GITHUB Release ----------
# Requires: GITHUB_REPO=owner/repo and GITHUB_TOKEN set or token file present
deploy_github() {
: "${GITHUB_REPO:?GITHUB_REPO missing}"
# Get token from env or file
if [[ -z "${GITHUB_TOKEN:-}" && -f "$TOKEN_FILE" ]]; then
GITHUB_TOKEN="$(tr -d '\r\n' < "$TOKEN_FILE")"
fi
[[ -n "${GITHUB_TOKEN:-}" ]] || die "GITHUB_TOKEN not available (set env var or provide TOKEN_FILE)"
local release_tag="${GITHUB_TAG_PREFIX:-}v$version"
local prerelease="${GITHUB_RELEASE_PRERELEASE:-0}"
# Prepare body
local body_file changelog_body
body_file="$(mktemp)"
changelog_body="$(sed ':a;N;$!ba;s/\r//g' "$CHANGELOG_FILE" \
| sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' \
| tr -d '\n')"
cat >"$body_file" <<JSON
{
"tag_name": "$release_tag",
"name": "$version",
"body": "$changelog_body",
"draft": false,
"prerelease": $( [[ "$prerelease" == "1" ]] && echo "true" || echo "false" )
}
JSON
# Check existing
status=$(curl -sS -o "$TMPDIR/github_release_response.json" -w "%{http_code}" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_REPO/releases/tags/$release_tag" || true)
release_id=""
if [[ "$status" == "200" ]]; then
release_id="$(grep -m1 -E '"id":[[:space:]]*[0-9]+' "$TMPDIR/github_release_response.json" | head -1 | sed -E 's/.*"id":[[:space:]]*([0-9]+).*/\1/')"
info "Release exists. Updating body (id=$release_id)..."
curl -sS -X PATCH "https://api.github.com/repos/$GITHUB_REPO/releases/$release_id" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "Content-Type: application/json" \
--data-binary "@$body_file" >/dev/null
else
info "Creating new release..."
curl -sS -X POST "https://api.github.com/repos/$GITHUB_REPO/releases" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "Content-Type: application/json" \
--data-binary "@$body_file" > "$TMPDIR/github_release_response.json"
release_id="$(grep -m1 -E '"id":[[:space:]]*[0-9]+' "$TMPDIR/github_release_response.json" | head -1 | sed -E 's/.*"id":[[:space:]]*([0-9]+).*/\1/')"
fi
rm -f "$body_file"
[[ -n "$release_id" ]] || { echo "[ERROR] Could not determine release ID."; cat "$TMPDIR/github_release_response.json" || true; exit 1; }
ok "Using Release ID: $release_id"
# Upload asset (replace if exists)
asset_name="$(basename "$zip_file")"
curl -sS -X POST "https://uploads.github.com/repos/$GITHUB_REPO/releases/$release_id/assets?name=$asset_name" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "Content-Type: application/zip" \
--data-binary @"$zip_file" >/dev/null || die "Asset upload failed"
ok "GitHub upload complete"
}
# ---------- Deploy: Google Drive (UUPD) ----------
deploy_drive() {
: "${GDRIVE_SYNC_DIR:?Missing GDRIVE_SYNC_DIR}"
: "${GDRIVE_ZIP_NAME:?Missing GDRIVE_ZIP_NAME}"
: "${GDRIVE_MANIFEST_NAME:?Missing GDRIVE_MANIFEST_NAME}"
# --- helper: accept raw fileId or any Google Drive URL
extract_drive_id() {
local in="$1"
in="${in//\"/}"; in="$(echo "$in" | tr -d '[:space:]')"
[[ "$in" =~ ^[A-Za-z0-9_-]{10,}$ ]] && { echo "$in"; return 0; }
[[ "$in" =~ /d/([A-Za-z0-9_-]{10,})/view ]] && { echo "${BASH_REMATCH[1]}"; return 0; }
[[ "$in" =~ (^|[?&])id=([A-Za-z0-9_-]{10,}) ]] && { echo "${BASH_REMATCH[2]}"; return 0; }
[[ "$in" =~ open\?id=([A-Za-z0-9_-]{10,}) ]] && { echo "${BASH_REMATCH[1]}"; return 0; }
echo ""; return 1
}
local drive_dir="$(tr -d '\r' <<<"$GDRIVE_SYNC_DIR")"
local drive_zip="$drive_dir/$GDRIVE_ZIP_NAME"
local drive_manifest="$drive_dir/$GDRIVE_MANIFEST_NAME"
# Normalize IDs (allow full links)
local ZIP_ID_RAW="${GDRIVE_ZIP_FILE_ID:-}"
local MANIFEST_ID_RAW="${GDRIVE_MANIFEST_FILE_ID:-}"
local ZIP_ID="$(extract_drive_id "$ZIP_ID_RAW")"
local MANIFEST_ID="$(extract_drive_id "$MANIFEST_ID_RAW")"
echo "[DEBUG] Drive dir : $drive_dir"
echo "[DEBUG] Drive zip path : $drive_zip"
echo "[DEBUG] Drive manifest : $drive_manifest"
echo "[DEBUG] ZIP_ID (parsed) : ${ZIP_ID:-<empty>}"
echo "[DEBUG] MANIFEST_ID (parsed): ${MANIFEST_ID:-<empty>}"
mkdir -p "$drive_dir" || die "Cannot create $drive_dir"
# Ensure files exist
if [[ ! -f "$drive_zip" ]]; then
echo "[INFO] Bootstrap: creating initial ZIP at $drive_zip"
else
echo "[INFO] Updating ZIP at $drive_zip"
fi
cp -f "$zip_file" "$drive_zip" || die "Copy to Drive folder failed"
if [[ ! -f "$drive_manifest" ]]; then
echo "[INFO] Bootstrap: creating empty manifest at $drive_manifest"
printf "{}" > "$drive_manifest" || die "Cannot create manifest file"
fi
# Optional helper: open folder if IDs missing
if [[ "${AUTO_OPEN_EXPLORER:-1}" == "1" ]] && { [[ -z "$ZIP_ID" ]] || [[ -z "$MANIFEST_ID" ]]; }; then
command -v explorer >/dev/null 2>&1 && explorer "$drive_dir" >/dev/null 2>&1 || true
fi
# Hash from the synced file (what users will download)
echo "[INFO] Computing SHA-256…"
local sha256
sha256="$(get_sha256 "$drive_zip")" || die "SHA-256 failure"
[[ -n "$sha256" ]] || die "SHA-256 is empty"
echo "[OK] SHA-256: $sha256"
# Guard: ZIP ID required to publish a valid package URL
if [[ -z "$ZIP_ID" && "${ALLOW_EMPTY_DRIVE_ID:-0}" != "1" ]]; then
echo "[SAFE-STOP] GDRIVE_ZIP_FILE_ID not set (or not parseable)."
echo " - Right-click '$GDRIVE_ZIP_NAME' in Drive web → View in web"
echo " - Copy the ID from /file/d/<ID>/view and paste into GDRIVE_ZIP_FILE_ID"
echo " - Re-run deploy."
return 1
fi
local package_url=""
[[ -n "$ZIP_ID" ]] && package_url="https://drive.google.com/uc?export=download&id=$ZIP_ID"
# ---------- Rich manifest fields ----------
local slug="$PLUGIN_SLUG"
local disp_name="${UUPD_NAME:-$PLUGIN_NAME}"
local author="${UUPD_AUTHOR:-}"
local author_home="${UUPD_AUTHOR_HOMEPAGE:-}"
# Requirements (prefer cfg overrides, fall back to headers extracted earlier)
local req_php="${UUPD_REQUIRES_PHP:-$requires_php}"
local req_wp="${UUPD_REQUIRES:-$requires_at_least}"
local tested_wp="${UUPD_TESTED:-$tested_up_to}"
# last_updated (allow override)
local last_updated="${UUPD_LAST_UPDATED:-$(date '+%Y-%m-%d %H:%M:%S')}"
# Download URL preference: cfg -> GitHub latest (if repo set) -> empty
local dl_url="${UUPD_DOWNLOAD_URL:-}"
if [[ -z "$dl_url" && -n "${GITHUB_REPO:-}" ]]; then
dl_url="https://github.com/$GITHUB_REPO/releases/latest/download/$ZIP_NAME"
fi
# Default banners/icons to your repo’s /uupd/ assets if not explicitly set
local gh_user="${GITHUB_REPO%%/*}"
local gh_repo="${GITHUB_REPO#*/}"
local raw_base=""
[[ -n "$gh_user" && -n "$gh_repo" ]] && raw_base="https://raw.githubusercontent.com/$gh_user/$gh_repo/main/uupd"
local banner_low="${UUPD_BANNER_LOW:-${raw_base:+$raw_base/banner-772x250.png}}"
local banner_high="${UUPD_BANNER_HIGH:-${raw_base:+$raw_base/banner-1544x500.png}}"
local icon_1x="${UUPD_ICON_1X:-${raw_base:+$raw_base/icon-128.png}}"
local icon_2x="${UUPD_ICON_2X:-${raw_base:+$raw_base/icon-256.png}}"
# Section files (paths may or may not exist)
local f_desc="${UUPD_SECTION_DESCRIPTION_FILE:-}"
local f_inst="${UUPD_SECTION_INSTALLATION_FILE:-}"
local f_faq="${UUPD_SECTION_FAQ_FILE:-}"
local f_chg_html="${UUPD_SECTION_CHANGELOG_HTML_FILE:-}"
# Build JSON with PHP via argv (Windows-safe). PHP will read section files if provided.
echo "[INFO] Building rich UUPD JSON…"
export MSYS2_ARG_CONV_EXCL='*' # prevent path mangling
# Defaults for banners/icons from your repo’s /uupd folder (optional)
gh_user="${GITHUB_REPO%%/*}"; gh_repo="${GITHUB_REPO#*/}"
cdn_path=""
[[ -n "$gh_user" && -n "$gh_repo" ]] && cdn_path="https://raw.githubusercontent.com/$gh_user/$gh_repo/main/uupd"
# Optional GitHub download_url fallback
dl_url="${UUPD_DOWNLOAD_URL:-}"
if [[ -z "$dl_url" && -n "${GITHUB_REPO:-}" ]]; then
dl_url="https://github.com/$GITHUB_REPO/releases/latest/download/$ZIP_NAME"
fi
php "$GDRIVE_JSON_SCRIPT" \
--plugin-file "$plugin_file" \
--readme-file "$STATIC_FILE" \
--changelog-file "$CHANGELOG_FILE" \
--out "$drive_manifest" \
--zip-id "$ZIP_ID" \
--sha "$sha256" \
--slug "$PLUGIN_SLUG" \
--cdn "$cdn_path" \
--download-url "$dl_url" \
${UUPD_NAME:+--name "$UUPD_NAME"} \
${UUPD_AUTHOR:+--author "$UUPD_AUTHOR"} \
${UUPD_AUTHOR_HOMEPAGE:+--author-url "$UUPD_AUTHOR_HOMEPAGE"} \
${UUPD_REQUIRES_PHP:+--requires-php "$UUPD_REQUIRES_PHP"} \
${UUPD_REQUIRES:+--requires "$UUPD_REQUIRES"} \
${UUPD_TESTED:+--tested "$UUPD_TESTED"} \
${UUPD_LAST_UPDATED:+--last "$UUPD_LAST_UPDATED"} \
${UUPD_BANNER_LOW:+--banner-low "$UUPD_BANNER_LOW"} \
${UUPD_BANNER_HIGH:+--banner-high "$UUPD_BANNER_HIGH"} \
${UUPD_ICON_1X:+--icon-1x "$UUPD_ICON_1X"} \
${UUPD_ICON_2X:+--icon-2x "$UUPD_ICON_2X"}
php_status=$?
if [[ $php_status -ne 0 ]]; then
echo "[ERROR] gdrivejson.php failed (exit=$php_status)"
exit 1
fi
echo "[OK] Manifest written: $drive_manifest"
ok "Drive manifest updated"
if [[ -n "$MANIFEST_ID" ]]; then
echo " Manifest URL : https://drive.google.com/uc?export=download&id=$MANIFEST_ID"
else
echo " Manifest URL : (set GDRIVE_MANIFEST_FILE_ID in cfg to print direct link)"
fi
if [[ -n "$ZIP_ID" ]]; then
echo " Package URL : https://drive.google.com/uc?export=download&id=$ZIP_ID"
else
echo " Package URL : (set GDRIVE_ZIP_FILE_ID to finalize)"
fi
echo " Note: ensure folder/files allow 'Anyone with the link - Viewer'."
}
# =====================================================
# ORCHESTRATE TARGETS
# =====================================================
# Normalize comma-separated list: "local,github,drive" -> "local github drive"
_targets="$(echo "${DEPLOY_TARGETS:-}" | tr ',' ' ' | tr -s ' ')"
if [[ -z "$_targets" ]]; then
echo "[INFO] No DEPLOY_TARGETS set. Skipping deployment."
else
echo "[INFO] Deploy targets: $_targets"
for target in $_targets; do
case "$target" in
local) deploy_local ;;
github) deploy_github ;;
drive) deploy_drive ;;
*) die "Unknown deploy target: $target" ;;
esac
done
fi
echo
echo "[OK] Deployment complete: $DEPLOY_TARGETS"
sleep 2
ShellScriptPHP
<?php
/**
* gdrivejson.php — build rich UUPD manifest from plugin headers + readme + changelog
*
* Examples:
* php gdrivejson.php \
* --plugin-file="path/to/plugin-slug/plugin-slug.php" \
* --readme-file="static.txt" \
* --changelog-file="C:/path/changelog.txt" \
* --out="G:/My Drive/trial/notify-for-suremail-update-info.json" \
* --zip-id="1KhT...UJWk" \
* --sha="56e465...10f02" \
* --slug="notify-for-suremail" \
* --cdn="https://raw.githubusercontent.com/user/repo/main/uupd" \
* --download-url="https://github.com/user/repo/releases/latest/download/plugin.zip"
*
* Any of these can be omitted and will be derived from headers/readme when possible:
* --name --author --author-url --requires-php --requires --tested
* Banners/Icons (optional):
* --banner-low --banner-high --icon-1x --icon-2x
*/
function args() {
$out = [];
$argv = $_SERVER['argv'];
array_shift($argv);
for ($i = 0; $i < count($argv); $i++) {
$a = $argv[$i];
if (strpos($a, '--') !== 0) continue;
$eq = strpos($a, '=');
if ($eq !== false) { $k = substr($a, 2, $eq-2); $v = substr($a, $eq+1); }
else { $k = substr($a, 2); $v = (isset($argv[$i+1]) && strpos($argv[$i+1], '--') !== 0) ? $argv[++$i] : '1'; }
$out[$k] = $v;
}
return $out;
}
function read_file($p) {
if (!$p) return '';
$p = str_replace('\\','/',$p);
return is_file($p) ? file_get_contents($p) : '';
}
function read_plugin_headers($file) {
$wanted = [
'Plugin Name' => '',
'Version' => '',
'Requires at least' => '',
'Tested up to' => '',
'Requires PHP' => '',
'Author' => '',
'Author URI' => '',
];
$data = read_file($file);
foreach ($wanted as $key => $val) {
if (preg_match('/^\\s*\\*?\\s*'.preg_quote($key,'/').'\\s*:\\s*(.+)$/mi', $data, $m)) {
$wanted[$key] = trim($m[1]);
}
}
return $wanted;
}
function parse_changelog_html($file) {
if (!is_file($file)) return '';
$lines = preg_split('/\\R/', read_file($file));
$html = '';
$open = false;
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') continue;
if (preg_match('/^=+\\s*(.+?)\\s*=+$/', $line, $m)) {
if ($open) $html .= '</ul>';
$html .= '<h4>' . htmlspecialchars($m[1]) . '</h4><ul>';
$open = true;
} else {
$html .= '<li>' . htmlspecialchars($line) . '</li>';
}
}
if ($open) $html .= '</ul>';
return $html;
}
function parse_readme_sections($file) {
if (!is_file($file)) return [];
$text = read_file($file);
$sections = [];
// == Heading == blocks
if (preg_match_all('/==\\s*(.*?)\\s*==\\s*(.*?)(?=(?:\\n==\\s*.*?\\s*==|\\z))/s', $text, $matches, PREG_SET_ORDER)) {
foreach ($matches as $m) {
$key = strtolower(str_replace(' ', '_', trim($m[1])));
$val = trim($m[2]);
// keep simple HTML line breaks
$sections[$key] = nl2br($val, false);
}
}
return $sections;
}
function banner_icon_defaults($cdn) {
if (!$cdn) return [null,null,null,null];
$cdn = rtrim($cdn, '/');
return [
$cdn . '/banner-772x250.png',
$cdn . '/banner-1544x500.png',
$cdn . '/icon-128.png',
$cdn . '/icon-256.png',
];
}
$opt = args();
$plugin_file = $opt['plugin-file'] ?? '';
$readme_file = $opt['readme-file'] ?? '';
$changelog_file = $opt['changelog-file'] ?? '';
$out_file = $opt['out'] ?? '';
$zip_id = $opt['zip-id'] ?? '';
$sha = $opt['sha'] ?? '';
if ($plugin_file === '' || !is_file($plugin_file)) {
fwrite(STDERR, "Missing/invalid --plugin-file\n"); exit(2);
}
if ($out_file === '') {
fwrite(STDERR, "Missing --out\n"); exit(2);
}
$hdr = read_plugin_headers($plugin_file);
$sections = parse_readme_sections($readme_file);
$changelog_html = parse_changelog_html($changelog_file);
if ($changelog_html !== '') {
$sections['changelog'] = $changelog_html;
}
// derive core fields, allow overrides
$slug = $opt['slug'] ?? (basename(dirname(str_replace('\\','/',$plugin_file))));
$name = $opt['name'] ?? ($hdr['Plugin Name'] ?: $slug);
$ver = $opt['version'] ?? ($hdr['Version'] ?: '');
$auth = $opt['author'] ?? ($hdr['Author'] ?: '');
$authu = $opt['author-url'] ?? ($hdr['Author URI'] ?: '');
$reqphp = $opt['requires-php'] ?? ($hdr['Requires PHP'] ?: '');
$reqwp = $opt['requires'] ?? ($hdr['Requires at least'] ?: '');
$testwp = $opt['tested'] ?? ($hdr['Tested up to'] ?: '');
$last = $opt['last'] ?? date('Y-m-d H:i:s');
$download_url = $opt['download-url'] ?? '';
list($def_blow,$def_bhigh,$def_i1,$def_i2) = banner_icon_defaults($opt['cdn'] ?? '');
$banner_low = $opt['banner-low'] ?? $def_blow ?? '';
$banner_high = $opt['banner-high'] ?? $def_bhigh ?? '';
$icon_1x = $opt['icon-1x'] ?? $def_i1 ?? '';
$icon_2x = $opt['icon-2x'] ?? $def_i2 ?? '';
$pkg = $zip_id ? ("https://drive.google.com/uc?export=download&id=".$zip_id) : '';
$out = [
'slug' => $slug,
'name' => $name,
'version' => $ver,
'author' => $auth,
'author_homepage' => $authu,
'requires_php' => $reqphp,
'requires' => $reqwp,
'tested' => $testwp,
'sections' => (object)$sections,
'last_updated' => $last,
'download_url' => $download_url,
'checksum' => ['algo' => 'sha256', 'hash' => $sha],
'banners' => ['low' => $banner_low, 'high' => $banner_high],
'icons' => ['1x' => $icon_1x, '2x' => $icon_2x],
];
$j = json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
if ($j === false) {
fwrite(STDERR, 'JSON encode failed: ' . json_last_error_msg() . PHP_EOL);
exit(3);
}
$dir = dirname($out_file);
if (!is_dir($dir) && !@mkdir($dir, 0777, true)) {
fwrite(STDERR, "Cannot create directory: {$dir}\n");
exit(4);
}
if (@file_put_contents($out_file, $j) === false) {
fwrite(STDERR, "Write failed: {$out_file}\n");
exit(5);
}
echo "OK: wrote {$out_file}\n";
PHP