Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7825f6be80 | ||
|
|
1e0a1308bf | ||
|
|
8caf8c0c01 | ||
|
|
589442e59c | ||
|
|
c90cbd7399 | ||
|
|
bb12cae954 |
21
.github/workflows/publish-release.yml
vendored
21
.github/workflows/publish-release.yml
vendored
@@ -166,3 +166,24 @@ jobs:
|
||||
body_path: /tmp/release-notes.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
make_latest: 'true'
|
||||
|
||||
# Belt-and-suspenders: action-gh-release@v2 has a long-standing
|
||||
# intermittent bug where it creates the release as a draft and silently
|
||||
# fails to flip the draft→published step, even though it reports success.
|
||||
# When that happens the install script + README snippets resolve "latest"
|
||||
# to whatever was last properly published, so users would get an old
|
||||
# version. We explicitly flip to published + latest here as a safety net;
|
||||
# if the action already did it correctly, this is a no-op.
|
||||
#
|
||||
# Security note: TAG and REPO are sourced from earlier `env:` blocks (not
|
||||
# interpolated inline into the run command), matching the same pattern
|
||||
# used elsewhere in this workflow.
|
||||
- name: Force-publish release
|
||||
if: steps.existing.outputs.skip != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
gh release edit "$TAG" --repo "$REPO" --draft=false --latest
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -2,6 +2,22 @@
|
||||
|
||||
All notable changes to the VirtFusion Direct Provisioning Module for WHMCS.
|
||||
|
||||
## [1.4.4] - 2026-04-25
|
||||
|
||||
### Bug Fixes
|
||||
- **`install.sh`: "TMP: unbound variable" error at script exit, plus exit code 1 even on successful installs.** The cleanup `trap 'rm -rf "$TMP"' EXIT` referenced a `local TMP` from inside `cmd_sync()`. The EXIT trap doesn't fire until the *shell* exits — by which time the function-scoped local is out of scope — and `set -u` then exploded the trap body, masking the real exit code with `1`. Fix: drop `local` so `TMP` persists at script scope until cleanup runs, and switch the trap body to `${TMP:-}` so any future regression that tightens TMP's scope still survives the trap. Cosmetic in practice (the install/upgrade work itself completed before the trap ran), but the non-zero exit broke automated wrappers and cron-driven invocations that check `$?`.
|
||||
|
||||
## [1.4.3] - 2026-04-25
|
||||
|
||||
### Features
|
||||
- **`install.sh` helper script with `install` / `upgrade` / `check` subcommands.** Single-file POSIX bash script that handles both first-time installation and upgrades, auto-detects the WHMCS web user from the parent directory's ownership and applies it to new files via rsync `--chown`, optionally syncs the PowerDNS reverse-DNS addon (`--with-addon`), accepts a pinned version (`--version v1.4.1`, default: latest published release), preserves any custom `config/ConfigOptionMapping.php` across the rsync `--delete`, and writes a `.installed-version` marker so the `check` subcommand can report installed-vs-latest without making changes. Pipeable via curl or wget. Exit codes for `check` (0=current, 1=outdated, 2=not installed) make it usable as a cron-driven update monitor. Closes the long-standing pitfall where rsyncing as root left files owned by `root:root` and the web server couldn't read them — the classic "module installed but invisible in WHMCS" symptom.
|
||||
|
||||
### Bug Fixes
|
||||
- **Release workflow now force-publishes new releases to non-draft and marks them `--latest`.** `softprops/action-gh-release@v2` has a long-standing intermittent bug where it creates a release as a draft and silently fails to flip it to published, despite reporting success. v1.4.0, v1.4.1, and v1.4.2 all shipped as drafts because of this — meaning the GitHub `releases/latest` API returned v1.3.0, the install snippets and the new `install.sh` would all download v1.3.0, and users would never get the storage-type-code fix even after running the documented upgrade. Added a `make_latest: 'true'` input to the action and a follow-up `gh release edit --draft=false --latest` step that runs unconditionally as a safety net. v1.4.0/1/2 were manually re-published as a one-off cleanup.
|
||||
|
||||
### Documentation
|
||||
- README install/upgrade sections rewritten to feature the `install.sh` script as the primary path (with both `curl` and `wget` examples), with the manual rsync recipe preserved in collapsible `<details>` blocks for users who prefer not to pipe scripts to bash. The manual recipe also gained a `stat -c '%U:%G'` ownership probe and `--chown="$OWNER"` flag, fixing the same root-owned-file pitfall the script handles automatically.
|
||||
|
||||
## [1.4.2] - 2026-04-25
|
||||
|
||||
### Documentation
|
||||
|
||||
61
README.md
61
README.md
@@ -130,17 +130,42 @@ You also need a VirtFusion API token with the following permissions:
|
||||
|
||||
## Installation
|
||||
|
||||
The fastest path is the install script. It auto-detects the WHMCS web user from your `modules/servers` directory ownership and applies it to the new files — without that, rsyncing as root would leave files owned by `root:root` and the web server couldn't read them ("module installed but invisible in WHMCS").
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/EZSCALE/virtfusion-whmcs-module/main/install.sh \
|
||||
| sudo bash -s -- install /path/to/whmcs
|
||||
```
|
||||
|
||||
Same thing with `wget`:
|
||||
```bash
|
||||
wget -qO- https://raw.githubusercontent.com/EZSCALE/virtfusion-whmcs-module/main/install.sh \
|
||||
| sudo bash -s -- install /path/to/whmcs
|
||||
```
|
||||
|
||||
Flags:
|
||||
- `--with-addon` — also install the PowerDNS reverse-DNS addon (`modules/addons/VirtFusionDns/`).
|
||||
- `--version v1.4.1` — pin a specific release tag (default: latest published release; any tag from [Releases](https://github.com/EZSCALE/virtfusion-whmcs-module/releases)).
|
||||
|
||||
The database table, schema migrations, and custom fields are all created automatically on first load.
|
||||
|
||||
<details>
|
||||
<summary><b>Manual install</b> (if you'd rather not pipe a script to bash)</summary>
|
||||
|
||||
```bash
|
||||
WHMCS=/path/to/whmcs
|
||||
VERSION=${VERSION:-$(curl -fsSL https://api.github.com/repos/EZSCALE/virtfusion-whmcs-module/releases/latest \
|
||||
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')}
|
||||
OWNER=$(stat -c '%U:%G' "$WHMCS/modules/servers")
|
||||
curl -fsSL "https://github.com/EZSCALE/virtfusion-whmcs-module/archive/refs/tags/${VERSION}.tar.gz" -o /tmp/vf.tar.gz \
|
||||
&& mkdir -p /tmp/vf && tar -xzf /tmp/vf.tar.gz -C /tmp/vf --strip-components=1 \
|
||||
&& rsync -ahP --delete /tmp/vf/modules/servers/VirtFusionDirect/ "$WHMCS/modules/servers/VirtFusionDirect/" \
|
||||
&& rsync -ahP --delete --chown="$OWNER" /tmp/vf/modules/servers/VirtFusionDirect/ "$WHMCS/modules/servers/VirtFusionDirect/" \
|
||||
&& rm -rf /tmp/vf /tmp/vf.tar.gz
|
||||
```
|
||||
|
||||
Set `WHMCS` once at the top — it's reused in every path below. The snippet defaults to the latest published release (queried live from the GitHub API); to pin a specific version, prepend `VERSION=v1.4.1` (or any tag from [Releases](https://github.com/EZSCALE/virtfusion-whmcs-module/releases)) before the command. The database table, schema migrations, and custom fields are all created automatically on first load.
|
||||
`--chown="$OWNER"` ensures the new files match your WHMCS web user (`www-data`, `apache`, etc.) instead of `root:root`. Requires rsync 3.1+ and root (or already running as the matching user). To pin a version, prepend `VERSION=v1.4.1` before the command.
|
||||
|
||||
</details>
|
||||
|
||||
Then configure in WHMCS Admin:
|
||||
|
||||
@@ -152,24 +177,42 @@ That's it. Hooks activate automatically and custom fields are created on module
|
||||
|
||||
## Upgrading
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/EZSCALE/virtfusion-whmcs-module/main/install.sh \
|
||||
| sudo bash -s -- upgrade /path/to/whmcs
|
||||
```
|
||||
|
||||
Add `--with-addon` if you also use the PowerDNS addon. Pin a version with `--version v1.4.1` for controlled rollouts or rollbacks. Addon settings live in `tbladdonmodules` and survive file updates. The script automatically backs up and restores any custom `config/ConfigOptionMapping.php` across the rsync `--delete`.
|
||||
|
||||
To check whether you're current without making any changes:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/EZSCALE/virtfusion-whmcs-module/main/install.sh \
|
||||
| bash -s -- check /path/to/whmcs
|
||||
```
|
||||
Exit codes: `0` = up-to-date, `1` = outdated (or version unknown), `2` = not installed. Useful in cron-driven monitoring.
|
||||
|
||||
If you use theme-overridden templates, review them for any new template variables. Clear the WHMCS template cache after upgrading: **Configuration > System Settings > General Settings > clear template cache**.
|
||||
|
||||
<details>
|
||||
<summary><b>Manual upgrade</b> (if you'd rather not pipe a script to bash)</summary>
|
||||
|
||||
```bash
|
||||
WHMCS=/path/to/whmcs
|
||||
VERSION=${VERSION:-$(curl -fsSL https://api.github.com/repos/EZSCALE/virtfusion-whmcs-module/releases/latest \
|
||||
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')}
|
||||
OWNER=$(stat -c '%U:%G' "$WHMCS/modules/servers")
|
||||
curl -fsSL "https://github.com/EZSCALE/virtfusion-whmcs-module/archive/refs/tags/${VERSION}.tar.gz" -o /tmp/vf.tar.gz \
|
||||
&& mkdir -p /tmp/vf && tar -xzf /tmp/vf.tar.gz -C /tmp/vf --strip-components=1 \
|
||||
&& rsync -ahP --delete /tmp/vf/modules/servers/VirtFusionDirect/ "$WHMCS/modules/servers/VirtFusionDirect/" \
|
||||
&& rsync -ahP --delete /tmp/vf/modules/addons/VirtFusionDns/ "$WHMCS/modules/addons/VirtFusionDns/" \
|
||||
&& rsync -ahP --delete --chown="$OWNER" /tmp/vf/modules/servers/VirtFusionDirect/ "$WHMCS/modules/servers/VirtFusionDirect/" \
|
||||
&& rsync -ahP --delete --chown="$OWNER" /tmp/vf/modules/addons/VirtFusionDns/ "$WHMCS/modules/addons/VirtFusionDns/" \
|
||||
&& rm -rf /tmp/vf /tmp/vf.tar.gz
|
||||
```
|
||||
|
||||
The second `rsync` line is only needed if you use the Reverse DNS addon; skip it otherwise. Addon settings live in `tbladdonmodules` and survive file updates.
|
||||
The second `rsync` line is only needed if you use the Reverse DNS addon; skip it otherwise.
|
||||
|
||||
The default behavior pulls the latest release. To pin a specific version (e.g. for a controlled rollout, or to roll back to a known-good version), prepend `VERSION=v1.4.1` (or any tag from [Releases](https://github.com/EZSCALE/virtfusion-whmcs-module/releases)) before the command.
|
||||
> **Note:** If you have a custom `config/ConfigOptionMapping.php`, back it up first — `--delete` will remove it. Restore it after. The helper script does this automatically.
|
||||
|
||||
> **Note:** If you have a custom `config/ConfigOptionMapping.php`, back it up first — `--delete` will remove it. Restore it after upgrading.
|
||||
|
||||
If you use theme-overridden templates, review them for any new template variables. Clear the WHMCS template cache after upgrading: **Configuration > System Settings > General Settings > clear template cache**.
|
||||
</details>
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
196
install.sh
Executable file
196
install.sh
Executable file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# install.sh — Manage the VirtFusion Direct WHMCS module.
|
||||
#
|
||||
# Subcommands:
|
||||
# install First-time install. Refuses if already present (use upgrade).
|
||||
# upgrade Refresh an existing install. Refuses if nothing is installed.
|
||||
# check Report installed version vs latest available. No changes.
|
||||
#
|
||||
# Flags (install/upgrade only):
|
||||
# --with-addon, -a Also sync the PowerDNS rDNS addon.
|
||||
# --version, -v vX.Y.Z Pin a specific release tag (default: latest).
|
||||
#
|
||||
# Exit codes for `check`:
|
||||
# 0 installed and up-to-date
|
||||
# 1 installed but outdated (or installed-version unknown)
|
||||
# 2 not installed
|
||||
#
|
||||
# Pipeable:
|
||||
# curl -fsSL https://raw.githubusercontent.com/EZSCALE/virtfusion-whmcs-module/main/install.sh \
|
||||
# | sudo bash -s -- install /path/to/whmcs
|
||||
#
|
||||
# wget -qO- https://raw.githubusercontent.com/EZSCALE/virtfusion-whmcs-module/main/install.sh \
|
||||
# | sudo bash -s -- upgrade --with-addon /path/to/whmcs
|
||||
#
|
||||
# curl -fsSL https://raw.githubusercontent.com/EZSCALE/virtfusion-whmcs-module/main/install.sh \
|
||||
# | bash -s -- check /path/to/whmcs
|
||||
#
|
||||
# Why a script? rsync into a directory owned by the WHMCS web user (e.g.
|
||||
# www-data, apache) lands files as root:root by default, which the web server
|
||||
# can't read — the classic "module installed but invisible in WHMCS" symptom.
|
||||
# This script reads the parent directory's owner and applies it via --chown, so
|
||||
# a `sudo bash` install ends up with correct ownership. It also preserves any
|
||||
# custom config/ConfigOptionMapping.php across --delete.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO="EZSCALE/virtfusion-whmcs-module"
|
||||
MARKER=".installed-version"
|
||||
|
||||
err() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; }
|
||||
warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; }
|
||||
info() { printf '\033[1;32m==>\033[0m %s\n' "$*"; }
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage:
|
||||
install.sh install [--with-addon] [--version vX.Y.Z] /path/to/whmcs
|
||||
install.sh upgrade [--with-addon] [--version vX.Y.Z] /path/to/whmcs
|
||||
install.sh check /path/to/whmcs
|
||||
|
||||
Examples:
|
||||
curl -fsSL https://raw.githubusercontent.com/$REPO/main/install.sh \\
|
||||
| sudo bash -s -- install /path/to/whmcs
|
||||
|
||||
wget -qO- https://raw.githubusercontent.com/$REPO/main/install.sh \\
|
||||
| sudo bash -s -- upgrade --with-addon /path/to/whmcs
|
||||
|
||||
curl -fsSL https://raw.githubusercontent.com/$REPO/main/install.sh \\
|
||||
| bash -s -- check /path/to/whmcs
|
||||
USAGE
|
||||
exit 2
|
||||
}
|
||||
|
||||
resolve_latest() {
|
||||
curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" \
|
||||
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p'
|
||||
}
|
||||
|
||||
read_installed_version() {
|
||||
local marker="$1/modules/servers/VirtFusionDirect/$MARKER"
|
||||
if [ -f "$marker" ]; then
|
||||
tr -d '[:space:]' < "$marker"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_check() {
|
||||
local WHMCS="$1"
|
||||
if [ ! -d "$WHMCS/modules/servers/VirtFusionDirect" ]; then
|
||||
warn "Not installed at $WHMCS"
|
||||
exit 2
|
||||
fi
|
||||
local current latest
|
||||
current=$(read_installed_version "$WHMCS")
|
||||
latest=$(resolve_latest)
|
||||
[ -n "$latest" ] || { err "Could not resolve latest version from GitHub API"; exit 1; }
|
||||
printf ' installed: %s\n latest: %s\n' "$current" "$latest"
|
||||
if [ "$current" = "$latest" ]; then
|
||||
info "Up to date"
|
||||
exit 0
|
||||
fi
|
||||
warn "Update available: $current → $latest"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cmd_sync() {
|
||||
local mode="$1"; shift
|
||||
local WITH_ADDON=0 VERSION="${VERSION:-}" WHMCS=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--with-addon|-a) WITH_ADDON=1; shift ;;
|
||||
--version|-v) VERSION="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
-*) err "Unknown flag: $1"; usage ;;
|
||||
*) WHMCS="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$WHMCS" ] || { err "Missing WHMCS path"; usage; }
|
||||
[ -d "$WHMCS/modules/servers" ] || {
|
||||
err "Not a WHMCS install: $WHMCS/modules/servers not found"; exit 1;
|
||||
}
|
||||
|
||||
local target="$WHMCS/modules/servers/VirtFusionDirect"
|
||||
if [ "$mode" = "install" ] && [ -d "$target" ]; then
|
||||
err "Already installed at $target — use 'upgrade' to refresh."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$mode" = "upgrade" ] && [ ! -d "$target" ]; then
|
||||
err "Not currently installed at $target — use 'install' instead."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION=$(resolve_latest)
|
||||
[ -n "$VERSION" ] || { err "Could not resolve latest version from GitHub API"; exit 1; }
|
||||
fi
|
||||
info "Target version: $VERSION"
|
||||
|
||||
local OWNER
|
||||
OWNER=$(stat -c '%U:%G' "$WHMCS/modules/servers" 2>/dev/null || true)
|
||||
[ -n "$OWNER" ] || { err "Could not detect parent directory owner via stat"; exit 1; }
|
||||
info "Owner (from $WHMCS/modules/servers): $OWNER"
|
||||
|
||||
# NOTE: TMP is intentionally NOT declared `local`. The EXIT trap fires when
|
||||
# the shell exits, not when this function returns — by then a function-local
|
||||
# would be out of scope and `set -u` would explode the trap body with
|
||||
# "TMP: unbound variable", masking the script's real exit code with 1.
|
||||
# The `${TMP:-}` expansion in the trap is belt-and-suspenders: harmless
|
||||
# if TMP somehow ends up unset, and prevents future regressions if anyone
|
||||
# moves the assignment back into a tighter scope.
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "${TMP:-}"' EXIT
|
||||
|
||||
info "Downloading $VERSION..."
|
||||
curl -fsSL "https://github.com/$REPO/archive/refs/tags/$VERSION.tar.gz" -o "$TMP/src.tar.gz"
|
||||
mkdir -p "$TMP/src"
|
||||
tar -xzf "$TMP/src.tar.gz" -C "$TMP/src" --strip-components=1
|
||||
|
||||
local SRC="$TMP/src/modules/servers/VirtFusionDirect"
|
||||
[ -d "$SRC" ] || { err "Tarball did not contain modules/servers/VirtFusionDirect"; exit 1; }
|
||||
|
||||
# Preserve user's custom configurable-option mapping across --delete.
|
||||
local MAP_FILE="$target/config/ConfigOptionMapping.php"
|
||||
local MAP_BACKUP=""
|
||||
if [ -f "$MAP_FILE" ]; then
|
||||
MAP_BACKUP="$TMP/ConfigOptionMapping.php.bak"
|
||||
cp -p "$MAP_FILE" "$MAP_BACKUP"
|
||||
info "Backed up custom ConfigOptionMapping.php"
|
||||
fi
|
||||
|
||||
info "Syncing server module → $target/"
|
||||
rsync -ahP --delete --chown="$OWNER" "$SRC/" "$target/"
|
||||
|
||||
if [ -n "$MAP_BACKUP" ]; then
|
||||
cp -p "$MAP_BACKUP" "$MAP_FILE"
|
||||
chown "$OWNER" "$MAP_FILE"
|
||||
info "Restored custom ConfigOptionMapping.php"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$VERSION" > "$target/$MARKER"
|
||||
chown "$OWNER" "$target/$MARKER"
|
||||
|
||||
if [ "$WITH_ADDON" = 1 ]; then
|
||||
local addon_src="$TMP/src/modules/addons/VirtFusionDns"
|
||||
local addon_target="$WHMCS/modules/addons/VirtFusionDns"
|
||||
[ -d "$addon_src" ] || { err "Tarball did not contain modules/addons/VirtFusionDns"; exit 1; }
|
||||
info "Syncing PowerDNS addon → $addon_target/"
|
||||
rsync -ahP --delete --chown="$OWNER" "$addon_src/" "$addon_target/"
|
||||
printf '%s\n' "$VERSION" > "$addon_target/$MARKER"
|
||||
chown "$OWNER" "$addon_target/$MARKER"
|
||||
fi
|
||||
|
||||
info "$mode complete: $VERSION (owner $OWNER)"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install) shift; cmd_sync install "$@" ;;
|
||||
upgrade) shift; cmd_sync upgrade "$@" ;;
|
||||
check) shift; [ $# -eq 1 ] || usage; cmd_check "$1" ;;
|
||||
-h|--help|"") usage ;;
|
||||
*) err "Unknown command: $1"; usage ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user