22 Commits
Author SHA1 Message Date
Kevin AdamsandClaude Sonnet 5 d2d20c339a fix: replace AnyEvent WebSocket transport with Protocol::WebSocket (#290)
AnyEvent::WebSocket::Client's blocking ->recv() cannot run inside
pveproxy/pvedaemon (themselves built on AnyEvent as their core
reactor) without nesting event loops, which AnyEvent refuses by
design -- confirmed by a real user on the first real WebUI use of
v4.0.0, one day after release. Not a race condition: this fails on
every real "Add Storage" attempt against a WebSocket-transport host,
unconditionally. v4.0.0's testing never caught it because every
verification script ran standalone, never inside the actual daemon
process -- pvesm (a fresh CLI process) doesn't trigger it either, only
the real REST API path through pveproxy does.

Replaced _ws_connect/_ws_call's internals with Protocol::WebSocket::Client
+ IO::Socket::SSL -- a plain synchronous socket client with zero
event-loop dependency, so there's no shared reactor state to conflict
with pveproxy's. _api_ws's method-mapping logic is completely
unchanged; only the connection/call internals were rewritten.

Reproduced the exact bug via a real POST to /api2/json/storage through
pveproxy on a lab node, then confirmed the fix resolves it the same
way. Re-ran every check from v4.0.0's release (read path, write path,
snapshots, multipath) against the new transport -- zero regressions.

Full story: ADR-014, superseding ADR-012's library-choice section
only (everything else in ADR-012 stands). $VERSION bumped to 4.0.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 21:58:05 -04:00
Kevin AdamsandClaude Sonnet 5 b2187654dc feat: implement WebSocket JSON-RPC 2.0 transport for TrueNAS SCALE 25.04+ (#243)
Adds _api_ws/_ws_connect/_ws_call and a _transport() auto-detector so
_api() dispatches to REST or WebSocket per host based on the detected
TrueNAS version -- storage.cfg is unchanged either way. Follows
ADR-012's decided design: AnyEvent::WebSocket::Client for the
transport, auth.login_with_api_key (not auth.login_ex, which needs a
username this plugin has no way to know), and the JSON-RPC envelope
hand-built with the existing JSON module rather than a dedicated RPC
library.

Live-verified end-to-end against .92 (TrueNAS-25.04.2.6, real SCALE
box) through the actual plugin module, not just the diagnostic script:
transport auto-detection, _api_global, portal/target queries, and the
single-ID lookup mapping used by path()/qemu_blockdev_options() all
confirmed working. This exercises every *read* (query) call the
plugin makes.

Caught and fixed one real bug during integration testing:
/system/product_type returns the license tier (e.g.
"COMMUNITY_EDITION"), not the product family -- CORE vs SCALE
detection uses the /system/version string's major-version magnitude
instead (SCALE's calendar versioning vs CORE's legacy 11.x-13.x range).

Scope note (see file header): the *write* path (create/delete/update
param shapes) follows TrueNAS's documented CRUDService conventions but
has not yet been independently live-tested -- verify against .91/.92
before trusting alloc_image/free_image/snapshot operations in
production on SCALE 25.04+.

$VERSION bumped to 4.0.0. Adds libanyevent-perl and
libanyevent-websocket-client-perl as package + CI lint dependencies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 10:13:34 -04:00
Kevin AdamsandClaude Sonnet 5 92c8beafe3 fix: restart HA daemons in truenas-proxmox-multipath postinst/postrm
Same root cause as #179: postinst/postrm only restarted pvedaemon/pveproxy,
never pvestatd/pve-ha-lrm/pve-ha-crm, so HA-managed VMs on truenas-multipath
storage could fail to start after install/upgrade. Mirrors the #179 fix —
bare statements so set -e aborts on a real failure, HA daemons restarted
via systemctl (they don't support a restart subcommand). Extended
test-restart-services.sh to cover both multipath scripts with the same
stub approach.

Fixes #287

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 14:28:32 -04:00
Kevin AdamsandClaude Sonnet 5 d60adfaaf7 fix: restart HA daemons on install/remove; add regression test (#179)
pvedaemon/pveproxy picked up the patched PVE::Storage state, but
pve-ha-lrm/pve-ha-crm kept running against the pre-install state, so
HA-managed VMs on TrueNAS storage failed to start. They don't support
a `restart` subcommand (only start/stop/status), so they're restarted
via systemctl instead. Also switched every restart from `cmd && log
...` to bare statements — set -e does not fire on the left side of
&&, so a genuine restart failure was previously swallowed silently
instead of aborting the script.

Adds tests/test-restart-services.sh: sources the real postinst/postrm
against stub PVE binaries in CI to catch both classes of regression
(wrong subcommand, silently-swallowed failure) without needing a live
PVE cluster. Real HA-cluster validation is documented as a manual
runbook (RUNBOOK-001) since GitHub Actions can't run pve-ha-lrm/crm.

Bumps $VERSION to 3.2.4.

Fixes #179

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 17:57:46 -04:00
Kevin AdamsandClaude Sonnet 5 55c2663163 fix: restart pvestatd and HA daemons on install/remove
pvedaemon/pveproxy picked up the patched PVE::Storage state, but
pve-ha-lrm/pve-ha-crm kept running against the pre-install state,
so HA-managed VMs on TrueNAS storage failed to start. pve-ha-lrm
and pve-ha-crm don't support a `restart` subcommand (only
start/stop/status), so they're restarted via systemctl instead.

Confirmed root cause by mir07/deamonkai on #179; verified the full
restart_pve_services() sequence end-to-end on pve01-hq before commit.

Fixes #179

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 17:30:09 -04:00
Kevin AdamsandClaude Sonnet 4.6 3e725244d0 fix: bump api() to 15 to match PVE 9 APIVER, silencing storage API warning
PVE 9.2.3 sets APIVER=15 in PVE::Storage. Our plugin was returning 14,
triggering "implementing an older storage API" on every pvedaemon/pveproxy
restart. No functional change — plugin loaded and worked correctly at 14.

Closes #282

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 14:00:46 -04:00
Kevin AdamsandClaude Sonnet 4.6 64b430f182 fix: create dists/error codename alias and fix cp -r overwrite bug
dists/error was never written to gh-pages because the codename alias
code was added after the v3.2.0 tag. The stable publish job only runs
on v*.*.* tags, so all subsequent pushes to release/3.x only touched
dists/testing. Bumping to v3.2.1 triggers stable publish to create it.

Also fix: cp -r src dst when dst already exists puts src inside dst
rather than replacing it. Add rm -rf before cp -r to guarantee a clean
replace on every stable release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 08:25:46 -04:00
Kevin AdamsandClaude Sonnet 4.6 b7acb605ee fix: strip pre-existing unmanaged devices block before injecting managed stanza
If /etc/multipath.conf already had a manual devices{} block before package
install, postinst would append a second identical block, causing multipathd
to emit "duplicate keyword: devices" warnings. Strip any unmanaged devices
block in the append path to keep the file clean.

Fixes #279. Discovered 2026-06-20 on pve01-hq during SCALE 25.04 multipath testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 12:07:40 -04:00
Kevin AdamsandClaude Sonnet 4.6 da823569a7 feat: add UI panel for truenas-multipath storage type (#278)
Without a registered JS panel the truenas-multipath type was invisible in
the PVE Datacenter → Storage → Add dropdown. Adds truenas-multipath.js
with all base TrueNAS fields plus the required truenas_portals field, and
wires it into the multipath package install/remove scripts and CI build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:23:01 -04:00
Kevin AdamsandClaude Sonnet 4.6 d0093f48b3 fix: safe multipath.conf management via BEGIN/END tagged block
On install: if /etc/multipath.conf already exists, append a tagged
block rather than skipping silently (old) or overwriting (dangerous).
On upgrade: replace only the tagged block, leaving the rest untouched.
On remove/purge: strip the tagged block; rest of the file preserved.

This means admins who added their own stanzas during manual testing
(e.g. before the package existed) won't lose config on install or
upgrade. Closes the silent "file already exists" footgun.

Also fixes pre-existing SC2015 shellcheck warnings in postrm.

Refs #256

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 21:54:12 -04:00
Kevin AdamsandClaude Sonnet 4.6 0d195a9074 feat: truenas-proxmox-multipath plugin and packaging (#256)
Adds TrueNASMultipath.pm — a separate PVE storage type 'truenas-multipath'
that inherits all REST API and iSCSI resource management from TrueNAS.pm but
uses iscsiadm + dm-multipath for block device access:

  path()             → /dev/mapper/<wwid>  (NAA from TrueNAS extent)
  activate_volume()  → iscsiadm login on each portal in truenas_portals;
                       waits for /dev/mapper device to appear
  deactivate_volume()→ multipath flush + iscsiadm logout
  qemu_blockdev_options → returns undef (path() used instead)

Packaging:
  packaging/DEBIAN-multipath/ — control.j2, postinst, postrm,
  changelog.Debian, multipath.conf.example
  Distributed via the 'multipath' apt component (ADR-011)
  Depends: truenas-proxmox >= 3.1.0, open-iscsi, multipath-tools

CI (build.yml):
  - Third staging dir (dist-multipath) and .deb output
  - multipath component pool under pool/multipath/v<major>
  - Both stable and testing publish steps updated to generate
    main + multipath components; Release lists both
  - Lint checks TrueNASMultipath.pm and multipath packaging scripts

PoC confirmed 2026-06-07 on pve01-hq against TrueNAS SCALE 24.10:
  - Two sessions (172.31.69.91 + 192.168.69.91), both active
  - dm-multipath aggregated 3 disks × 2 paths each
  - /dev/mapper/mpath* devices appeared correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 08:52:47 -04:00
Kevin AdamsandClaude Sonnet 4.6 1c98591ed4 fix: add Debian changelog to both packages (#273)
Adds packaging/changelog.Debian and packaging/DEBIAN-transitional/changelog.Debian
and wires them into the build staging steps. The gzipped changelog lands at
/usr/share/doc/truenas-proxmox/changelog.Debian.gz and
/usr/share/doc/freenas-proxmox/changelog.Debian.gz respectively, satisfying
Debian policy §12.7 and silencing the 'apt changelog' failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 09:02:24 -04:00
Kevin AdamsandClaude Sonnet 4.6 9b2ecb8093 chore: rename package freenas-proxmox → truenas-proxmox (#262)
Introduces truenas-proxmox as the real package and freenas-proxmox as a
transitional meta-package that Depends on truenas-proxmox. Existing
users running apt upgrade automatically pull in the new package; the
migration one-liner (apt install truenas-proxmox && apt purge
freenas-proxmox) completes the transition. The freenas-proxmox package
name will not exist in v4.0.

Changes:
- packaging/DEBIAN/: Package renamed to truenas-proxmox, INSTALL_DIR
  and LOG_FILE updated to /usr/share/truenas-proxmox and
  /var/log/truenas-proxmox-install.log; Replaces/Breaks fields added
- packaging/DEBIAN-transitional/: new empty meta-package that pulls in
  truenas-proxmox and prints a deprecation notice on configure
- build.yml: PACKAGE_NAME=truenas-proxmox; both debs built and
  published to Cloudsmith; transitional postinst added to shellcheck
- All GitHub repo URLs updated to TheGrandWazoo/truenas-proxmox
- apt install/remove/purge commands in docs updated to truenas-proxmox
- GitHub repo rename to truenas-proxmox pending (gh repo rename step)

Closes #262

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:49:28 -04:00
Kevin AdamsandClaude Sonnet 4.6 c48a992385 feat: add Help button to TrueNAS storage panel and ship docs
- Inject pveOnlineHelpInfo entry in truenas-storage.js so PVE wires
  the Help button automatically via the StorageBase framework
- Add onlineHelp: 'storage_truenas' to the input panel definition
- Add ui/truenas-storage-help.html — local help page installed to
  /usr/share/pve-docs/truenas-storage.html (no internet required)
- Add docs/getting-started.md and docs/architecture.md
- Update postinst/postrm to install and remove the help HTML file
- Update build.yml to include the help HTML in the package staging dir

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:08:41 -04:00
Kevin AdamsandClaude Sonnet 4.6 8a487ef4a8 feat: add TrueNAS UI panel via injected JS — no pvemanagerlib.js patch
Instead of diffing and patching the 50k-line pvemanagerlib.js each PVE
release, we ship truenas-storage.js as our own file and inject one
<script> line into index.html.tpl (rarely changes, ~60 lines).

truenas-storage.js adds 'truenas' to PVE.Utils.storageSchema at runtime
and defines PVE.storage.TrueNASInputPanel with all config fields:
  - TrueNAS host, API key, pool, dataset (column 1)
  - SSL toggle, cert verify, portal IP, target IQN (column 2)
API key field: required on create, optional on edit (only submitted if
the user types a new value).

postinst: copies TrueNAS.pm + truenas-storage.js, adds script tag
postrm:   removes both files, strips script tag

Closes #225 (core interface), #226 (iSCSI lifecycle activate/deactivate),
#227 (API pool listing replaces SSH).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:50:15 -04:00
Kevin AdamsandClaude Sonnet 4.6 33faf6eb34 chore: v3.0 packaging cleanup — no patches, one .pm file
- Rename Custom/FreeNAS.pm → Custom/TrueNAS.pm (matches package name)
- Rewrite postinst: copy TrueNAS.pm + restart pveproxy (no patch logic)
- Rewrite postrm: remove TrueNAS.pm + restart (no restore-orig logic)
- Delete triggers: nothing to watch (no ZFSPlugin/pvemanagerlib/apidoc)
- Drop 'patch' dep, add 'open-iscsi'; update package description
- Delete patch-generation runbook (obsolete)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 19:55:59 -04:00
Kevin AdamsandClaude Sonnet 4.6 3b3cc8d135 fix: ZFSPlugin 8.4.x patch and missing patch dependency (#236 #237)
Two install-time failures on PVE 8.4.x nodes:

1. 'patch' utility not in Depends — causes silent patch failure on fresh
   nodes; all three PVE file patches silently skip, plugin appears to
   install but storage operations fail. Added 'patch' to control.j2.

2. ZFSPlugin.pm routing not applied on PVE 8.4.x — tabs→spaces reformat
   and get_base($scfg) signature change in 8.4 broke hunks #3 and #4
   (the freenas elsif routing branches). Nodes with a prior partial
   install are doubly affected: grep idempotency check passes on partial
   freenas strings, so the routing is never applied, causing:
     "freenas: unknown iscsi provider" on clone/migrate operations.

New ZFSPlugin-8.4.14_1.pm.patch generated against stock PVE 8.4.19,
bundled as patches/ZFSPlugin/8.4.patch alongside existing 8.patch.

Verified clean on pve01-hq (8.4.19) and pve02-hq (8.4.14).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:16:17 -04:00
Kevin AdamsandClaude Sonnet 4.6 b41fefb72b docs: add PVE version compatibility warnings across all user touchpoints
Users on unsupported PVE versions now get clear guidance at every
layer — README, install time, and GitHub release notes — rather than
silently failing or getting confusing patch errors.

- README: replace bare compatibility table with a full matrix and
  prominent callout blocks for PVE 7 (last v2.x), PVE 8 (EOL
  2026-08-31), PVE 9+ (use v3.0), and PVE ≤6 (unsupported)
- postinst: add check_pve_version() that exits on PVE < 7, warns
  loudly on PVE 7 (stay on v2.x, do not upgrade to v3.0), notes
  the PVE 8 EOL date, and warns on PVE 9+ (v2.x not supported)
- build.yml release template: add a version compatibility table so
  every GitHub release prominently states who should and should not
  install it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:33:13 -04:00
Kevin AdamsandClaude Sonnet 4.6 ea1d0e2248 fix: version-aware pvemanagerlib patch selection for PVE 8.4.x (#223)
PVE 8.4 reformatted pvemanagerlib.js (tabs→spaces, structural changes),
making the existing 8.0.5-era patch fail on all 8.4.x nodes.

- Add pvemanagerlib-8.4.14_1.js.patch covering PVE 8.4.x (all hunks
  verified on 8.4.14 and 8.4.19)
- Update postinst to detect pve-manager major.minor (e.g. "8.4") and
  try a minor-version-specific patch before falling back to the major
  patch — 8.3.x nodes keep using 8.patch, 8.4.x nodes use 8.4.patch
- Update build.yml to bundle 8.4.patch alongside the existing 8.patch

Tested live on pve01-hq (8.4.19) and pve03-hq (8.3.2) — correct patch
selected in each case, pvemanagerlib patched successfully on 8.4.19.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:10:11 -04:00
Kevin AdamsandClaude Sonnet 4.6 ac58bf75de Fix DEBIAN control.j2: use binary-only format for dpkg-deb
dpkg-deb --build reads a binary control file and rejects the two-stanza
source+binary format. Rewrote control.j2 as a single binary-only stanza.
Removed the source stanza (Source, Standards-Version, Build-Depends fields)
which are only meaningful in source packages built with dpkg-buildpackage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:16:20 -04:00
Kevin AdamsandClaude Sonnet 4.6 0606ffb1a4 Fix shellcheck SC2034: remove unused REST_CLIENT_PATH variable in postrm
REST_CLIENT_PATH was defined but never referenced. REST/Client.pm is owned
by librest-client-perl and is intentionally not removed by our postrm
(documented in the comment above remove_plugin_files). Remove the dead variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:10:45 -04:00
Kevin AdamsandClaude Sonnet 4.6 636cd06ff6 Phase 1 project revival + PR fixes (#207, #209, #213)
Project infrastructure:
- Replace MIT license with AGPL-3.0 (KSA Technologies, LLC)
- Full README rewrite with badges, compatibility table, token auth docs
- Add CHANGELOG, CONTRIBUTING, DONORS, SECURITY docs
- Add GitHub issue/PR templates, update FUNDING.yml and stale.yml
- Replace external packer repo dispatch with self-contained CI (build.yml)
- Add packaging/DEBIAN/ with postinst/postrm/triggers (no git clone at install)
- Add .perlcriticrc for static analysis
- Add .claude/cos/ ADRs, plans, and runbooks

Bug fixes from community PRs:
- Fix bearer token check in freenas_api_connect: defined() && value instead of
  defined() alone, so truenas_token_auth=0 no longer activates Bearer Token auth (#207)
- Fix LUN 0 falsy bug in ZFSPlugin patch: !$guid -> !defined $guid in both
  zfs_get_lun_number and zfs_get_wwid_number, fixing VMs on LUN 0 for PVE 9 (#209)
- Fix syslog typo "wtih" -> "with" in run_list_extent (#213)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 14:22:04 -04:00