From 817433a39350fe8cfb3afd5c6a97d4b61f27e382 Mon Sep 17 00:00:00 2001 From: Kevin Scott Adams Date: Sat, 6 Jan 2024 17:34:17 -0500 Subject: [PATCH 01/28] Clean up code. - Indent and whitespace cleanup. --- pve-manager/js/pvemanagerlib-8.0.5_1.js.patch | 286 ++++++++++++++++++ pve-manager/js/pvemanagerlib.js.patch | 223 +++++++------- 2 files changed, 396 insertions(+), 113 deletions(-) create mode 100644 pve-manager/js/pvemanagerlib-8.0.5_1.js.patch diff --git a/pve-manager/js/pvemanagerlib-8.0.5_1.js.patch b/pve-manager/js/pvemanagerlib-8.0.5_1.js.patch new file mode 100644 index 0000000..e01515f --- /dev/null +++ b/pve-manager/js/pvemanagerlib-8.0.5_1.js.patch @@ -0,0 +1,286 @@ +--- pvemanagerlib.js.orig 2024-01-06 14:50:39.890252726 -0500 ++++ pvemanagerlib.js 2024-01-06 15:51:53.708260622 -0500 +@@ -9228,6 +9228,7 @@ + alias: ['widget.pveiScsiProviderSelector'], + comboItems: [ + ['comstar', 'Comstar'], ++ ['freenas', 'FreeNAS/TrueNAS API'], + ['istgt', 'istgt'], + ['iet', 'IET'], + ['LIO', 'LIO'], +@@ -58017,16 +58018,23 @@ + me.callParent(); + }, + }); ++ + Ext.define('PVE.storage.ZFSInputPanel', { + extend: 'PVE.panel.StorageBase', +- + viewModel: { +- parent: null, +- data: { +- isLIO: false, +- isComstar: true, +- hasWriteCacheOption: true, +- }, ++ parent: null, ++ data: { ++ isComstar: true, ++ isFreeNAS: false, ++ isLIO: false, ++ isToken: false, ++ hasWriteCacheOption: true, ++ }, ++ formulas: { ++ hideUsername: function(get) { ++ return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken'))); ++ }, ++ }, + }, + + controller: { +@@ -58035,12 +58043,41 @@ + 'field[name=iscsiprovider]': { + change: 'changeISCSIProvider', + }, ++ 'field[name=truenas_token_auth]': { ++ change: 'changeUsername', ++ }, + }, + changeISCSIProvider: function(f, newVal, oldVal) { ++ var me = this; + var vm = this.getViewModel(); + vm.set('isLIO', newVal === 'LIO'); + vm.set('isComstar', newVal === 'comstar'); +- vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt'); ++ vm.set('isFreeNAS', newVal === 'freenas'); ++ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt'); ++ if (newVal !== 'freenas') { ++ me.lookupReference('freenas_use_ssl_field').setValue(false); ++ me.lookupReference('truenas_token_auth_field').setValue(false); ++ me.lookupReference('freenas_apiv4_host_field').setValue(''); ++ me.lookupReference('freenas_user_field').setValue(''); ++ me.lookupReference('freenas_user_field').allowBlank = true; ++ me.lookupReference('truenas_secret_field').setValue(''); ++ me.lookupReference('truenas_secret_field').allowBlank = true; ++ me.lookupReference('truenas_confirm_secret_field').setValue(''); ++ me.lookupReference('truenas_confirm_secret_field').allowBlank = true; ++ } else { ++ me.lookupReference('freenas_user_field').allowBlank = false; ++ me.lookupReference('truenas_secret_field').allowBlank = false; ++ me.lookupReference('truenas_confirm_secret_field').allowBlank = false; ++ } ++ }, ++ changeUsername: function(f, newVal, oldVal) { ++ var me = this; ++ var vm = me.getViewModel(); ++ vm.set('isToken', newVal); ++ me.lookupReference('freenas_user_field').allowBlank = newVal; ++ if (newVal) { ++ me.lookupReference('freenas_user_field').setValue(''); ++ } + }, + }, + +@@ -58053,18 +58090,68 @@ + + values.nowritecache = values.writecache ? 0 : 1; + delete values.writecache; ++ console.warn(values.freenas_password); ++ if (values.freenas_password) { ++ values.truenas_secret = values.freenas_password; ++ } ++ console.warn(values.truenas_secret); + + return me.callParent([values]); + }, + + setValues: function(values) { +- values.writecache = values.nowritecache ? 0 : 1; +- this.callParent([values]); ++ if (values.freenas_password) { ++ values.truenas_secret = values.freenas_password; ++ } ++ values.truenas_confirm_secret = values.truenas_secret; ++ values.writecache = values.nowritecache ? 0 : 1; ++ this.callParent([values]); + }, + + initComponent: function() { + var me = this; + ++ var tnsecret = Ext.create('Ext.form.TextField', { ++ xtype: 'proxmoxtextfield', ++ name: 'truenas_secret', ++ reference: 'truenas_secret_field', ++ inputType: me.isCreate ? '' : 'password', ++ value: '', ++ editable: true, ++ emptyText: Proxmox.Utils.noneText, ++ bind: { ++ hidden: '{!isFreeNAS}' ++ }, ++ fieldLabel: gettext('API Password'), ++ change: function(f, value) { ++ if (f.rendered) { ++ f.up().down('field[name=truenas_confirm_secret]').validate(); ++ } ++ }, ++ }); ++ ++ var tnconfirmsecret = Ext.create('Ext.form.TextField', { ++ xtype: 'proxmoxtextfield', ++ name: 'truenas_confirm_secret', ++ reference: 'truenas_confirm_secret_field', ++ inputType: me.isCreate ? '' : 'password', ++ value: '', ++ editable: true, ++ submitValue: false, ++ emptyText: Proxmox.Utils.noneText, ++ bind: { ++ hidden: '{!isFreeNAS}' ++ }, ++ fieldLabel: gettext('Confirm API Password'), ++ validator: function(value) { ++ var pw = me.up().down('field[name=truenas_secret]').getValue(); ++ if (pw !== value) { ++ return "Secrets do not match!"; ++ } ++ return true; ++ }, ++ }); ++ + me.column1 = [ + { + xtype: me.isCreate ? 'textfield' : 'displayfield', +@@ -58074,7 +58161,7 @@ + allowBlank: false, + }, + { +- xtype: me.isCreate ? 'textfield' : 'displayfield', ++ xtype: 'textfield', + name: 'pool', + value: '', + fieldLabel: gettext('Pool'), +@@ -58084,11 +58171,11 @@ + xtype: me.isCreate ? 'textfield' : 'displayfield', + name: 'blocksize', + value: '4k', +- fieldLabel: gettext('Block Size'), ++ fieldLabel: gettext('ZFS Block Size'), + allowBlank: false, + }, + { +- xtype: me.isCreate ? 'textfield' : 'displayfield', ++ xtype: 'textfield', + name: 'target', + value: '', + fieldLabel: gettext('Target'), +@@ -58099,9 +58186,60 @@ + name: 'comstar_tg', + value: '', + fieldLabel: gettext('Target group'), +- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' }, ++ bind: { ++ hidden: '{!isComstar}' ++ }, + allowBlank: true, + }, ++ { ++ xtype: 'proxmoxcheckbox', ++ name: 'freenas_use_ssl', ++ reference: 'freenas_use_ssl_field', ++ inputId: 'freenas_use_ssl_field', ++ checked: false, ++ bind: { ++ hidden: '{!isFreeNAS}' ++ }, ++ uncheckedValue: 0, ++ fieldLabel: gettext('API use SSL'), ++ }, ++ { ++ xtype: 'proxmoxcheckbox', ++ name: 'truenas_token_auth', ++ reference: 'truenas_token_auth_field', ++ inputId: 'truenas_use_token_auth_field', ++ checked: false, ++ listeners: { ++ change: function(field, newValue) { ++ if (newValue === true) { ++ tnsecret.labelEl.update('API Token'); ++ tnconfirmsecret.labelEl.update('Confirm API Token'); ++ me.lookupReference('freenas_user_field').setValue(''); ++ me.lookupReference('freenas_user_field').allowBlank = true; ++ } else { ++ tnsecret.labelEl.update('API Password'); ++ tnconfirmsecret.labelEl.update('Confirm API Password'); ++ me.lookupReference('freenas_user_field').allowBlank = false; ++ } ++ }, ++ }, ++ bind: { ++ hidden: '{!isFreeNAS}' ++ }, ++ uncheckedValue: 0, ++ fieldLabel: gettext('API Token Auth'), ++ }, ++ { ++ xtype: 'textfield', ++ name: 'freenas_user', ++ reference: 'freenas_user_field', ++ inputId: 'freenas_user_field', ++ value: '', ++ fieldLabel: gettext('API Username'), ++ bind: { ++ hidden: '{hideUsername}' ++ }, ++ }, + ]; + + me.column2 = [ +@@ -58131,7 +58269,9 @@ + xtype: me.isCreate ? 'textfield' : 'displayfield', + name: 'comstar_hg', + value: '', +- bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' }, ++ bind: { ++ hidden: '{!isComstar}' ++ }, + fieldLabel: gettext('Host group'), + allowBlank: true, + }, +@@ -58139,15 +58279,32 @@ + xtype: me.isCreate ? 'textfield' : 'displayfield', + name: 'lio_tpg', + value: '', +- bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' }, +- allowBlank: false, ++ bind: { ++ hidden: '{!isLIO}' ++ }, + fieldLabel: gettext('Target portal group'), ++ allowBlank: true ++ }, ++ { ++ xtype: 'proxmoxtextfield', ++ name: 'freenas_apiv4_host', ++ reference: 'freenas_apiv4_host_field', ++ value: '', ++ editable: true, ++ emptyText: Proxmox.Utils.noneText, ++ bind: { ++ hidden: '{!isFreeNAS}' ++ }, ++ fieldLabel: gettext('API IPv4 Host'), + }, ++ tnsecret, ++ tnconfirmsecret, + ]; + + me.callParent(); + }, + }); ++ + Ext.define('PVE.storage.ZFSPoolSelector', { + extend: 'PVE.form.ComboBoxSetStoreNode', + alias: 'widget.pveZFSPoolSelector', diff --git a/pve-manager/js/pvemanagerlib.js.patch b/pve-manager/js/pvemanagerlib.js.patch index 9fa45ec..e01515f 100644 --- a/pve-manager/js/pvemanagerlib.js.patch +++ b/pve-manager/js/pvemanagerlib.js.patch @@ -1,88 +1,92 @@ ---- pvemanagerlib.js.orig 2023-12-30 15:36:27.913505863 -0500 -+++ pvemanagerlib.js 2024-01-02 09:30:56.000000000 -0500 +--- pvemanagerlib.js.orig 2024-01-06 14:50:39.890252726 -0500 ++++ pvemanagerlib.js 2024-01-06 15:51:53.708260622 -0500 @@ -9228,6 +9228,7 @@ alias: ['widget.pveiScsiProviderSelector'], comboItems: [ ['comstar', 'Comstar'], -+ ['freenas', 'FreeNAS/TrueNAS API'], ++ ['freenas', 'FreeNAS/TrueNAS API'], ['istgt', 'istgt'], ['iet', 'IET'], ['LIO', 'LIO'], -@@ -58017,16 +58018,24 @@ +@@ -58017,16 +58018,23 @@ me.callParent(); }, }); + Ext.define('PVE.storage.ZFSInputPanel', { extend: 'PVE.panel.StorageBase', - +- viewModel: { - parent: null, - data: { -+isComstar: true, -+ isFreeNAS: false, - isLIO: false, +- parent: null, +- data: { +- isLIO: false, - isComstar: true, -+ isToken: false, - hasWriteCacheOption: true, - }, -+formulas: { -+ hideUsername: function(get) { -+ return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken'))); -+ }, -+ }, +- hasWriteCacheOption: true, +- }, ++ parent: null, ++ data: { ++ isComstar: true, ++ isFreeNAS: false, ++ isLIO: false, ++ isToken: false, ++ hasWriteCacheOption: true, ++ }, ++ formulas: { ++ hideUsername: function(get) { ++ return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken'))); ++ }, ++ }, }, controller: { -@@ -58034,13 +58043,42 @@ - control: { +@@ -58035,12 +58043,41 @@ 'field[name=iscsiprovider]': { change: 'changeISCSIProvider', -+}, + }, + 'field[name=truenas_token_auth]': { + change: 'changeUsername', - }, ++ }, }, changeISCSIProvider: function(f, newVal, oldVal) { -+var me = this; ++ var me = this; var vm = this.getViewModel(); vm.set('isLIO', newVal === 'LIO'); vm.set('isComstar', newVal === 'comstar'); - vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt'); + vm.set('isFreeNAS', newVal === 'freenas'); -+ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt'); -+ if (newVal !== 'freenas') { -+ me.lookupReference('freenas_use_ssl_field').setValue(false); -+ me.lookupReference('truenas_token_auth_field').setValue(false); -+ me.lookupReference('freenas_apiv4_host_field').setValue(''); -+ me.lookupReference('freenas_user_field').setValue(''); -+ me.lookupReference('freenas_user_field').allowBlank = true; -+ me.lookupReference('truenas_secret_field').setValue(''); -+ me.lookupReference('truenas_secret_field').allowBlank = true; -+ me.lookupReference('truenas_confirm_secret_field').setValue(''); -+ me.lookupReference('truenas_confirm_secret_field').allowBlank = true; -+ } else { -+ me.lookupReference('freenas_user_field').allowBlank = false; -+ me.lookupReference('truenas_secret_field').allowBlank = false; -+ me.lookupReference('truenas_confirm_secret_field').allowBlank = false; -+ } -+ }, -+ changeUsername: function(f, newVal, oldVal) { -+ var me = this; -+ var vm = me.getViewModel(); -+ vm.set('isToken', newVal); -+ me.lookupReference('freenas_user_field').allowBlank = newVal; -+ if (newVal) { -+ me.lookupReference('freenas_user_field').setValue(''); -+ } ++ vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt'); ++ if (newVal !== 'freenas') { ++ me.lookupReference('freenas_use_ssl_field').setValue(false); ++ me.lookupReference('truenas_token_auth_field').setValue(false); ++ me.lookupReference('freenas_apiv4_host_field').setValue(''); ++ me.lookupReference('freenas_user_field').setValue(''); ++ me.lookupReference('freenas_user_field').allowBlank = true; ++ me.lookupReference('truenas_secret_field').setValue(''); ++ me.lookupReference('truenas_secret_field').allowBlank = true; ++ me.lookupReference('truenas_confirm_secret_field').setValue(''); ++ me.lookupReference('truenas_confirm_secret_field').allowBlank = true; ++ } else { ++ me.lookupReference('freenas_user_field').allowBlank = false; ++ me.lookupReference('truenas_secret_field').allowBlank = false; ++ me.lookupReference('truenas_confirm_secret_field').allowBlank = false; ++ } ++ }, ++ changeUsername: function(f, newVal, oldVal) { ++ var me = this; ++ var vm = me.getViewModel(); ++ vm.set('isToken', newVal); ++ me.lookupReference('freenas_user_field').allowBlank = newVal; ++ if (newVal) { ++ me.lookupReference('freenas_user_field').setValue(''); ++ } }, }, -@@ -58053,28 +58091,78 @@ +@@ -58053,18 +58090,68 @@ values.nowritecache = values.writecache ? 0 : 1; delete values.writecache; -+ console.warn(values.freenas_password); ++ console.warn(values.freenas_password); + if (values.freenas_password) { + values.truenas_secret = values.freenas_password; + } @@ -103,60 +107,53 @@ }, initComponent: function() { -- var me = this; -+ var me = this; -+ -+ var tnsecret = Ext.create('Ext.form.TextField', { -+ xtype: 'proxmoxtextfield', -+ name: 'truenas_secret', -+ reference: 'truenas_secret_field', -+ inputType: me.isCreate ? '' : 'password', -+ value: '', -+ editable: true, -+ emptyText: Proxmox.Utils.noneText, -+ bind: { -+ hidden: '{!isFreeNAS}' -+ }, -+ fieldLabel: gettext('API Password'), -+ change: function(f, value) { -+ if (f.rendered) { -+ f.up().down('field[name=truenas_confirm_secret]').validate(); -+ } -+ }, -+ }); + var me = this; -- me.column1 = [ -- { -- xtype: me.isCreate ? 'textfield' : 'displayfield', -- name: 'portal', -+ var tnconfirmsecret = Ext.create('Ext.form.TextField', { -+ xtype: 'proxmoxtextfield', -+ name: 'truenas_confirm_secret', -+ reference: 'truenas_confirm_secret_field', -+ inputType: me.isCreate ? '' : 'password', -+ value: '', -+ editable: true, -+ submitValue: false, -+ emptyText: Proxmox.Utils.noneText, -+ bind: { -+ hidden: '{!isFreeNAS}' -+ }, -+ fieldLabel: gettext('Confirm API Password'), -+ validator: function(value) { -+ var pw = me.up().down('field[name=truenas_secret]').getValue(); -+ if (pw !== value) { -+ return "Secrets do not match!"; -+ } -+ return true; -+ }, -+ }); ++ var tnsecret = Ext.create('Ext.form.TextField', { ++ xtype: 'proxmoxtextfield', ++ name: 'truenas_secret', ++ reference: 'truenas_secret_field', ++ inputType: me.isCreate ? '' : 'password', ++ value: '', ++ editable: true, ++ emptyText: Proxmox.Utils.noneText, ++ bind: { ++ hidden: '{!isFreeNAS}' ++ }, ++ fieldLabel: gettext('API Password'), ++ change: function(f, value) { ++ if (f.rendered) { ++ f.up().down('field[name=truenas_confirm_secret]').validate(); ++ } ++ }, ++ }); + -+ me.column1 = [ -+ { -+ xtype: me.isCreate ? 'textfield' : 'displayfield', -+ name: 'portal', - value: '', - fieldLabel: gettext('Portal'), ++ var tnconfirmsecret = Ext.create('Ext.form.TextField', { ++ xtype: 'proxmoxtextfield', ++ name: 'truenas_confirm_secret', ++ reference: 'truenas_confirm_secret_field', ++ inputType: me.isCreate ? '' : 'password', ++ value: '', ++ editable: true, ++ submitValue: false, ++ emptyText: Proxmox.Utils.noneText, ++ bind: { ++ hidden: '{!isFreeNAS}' ++ }, ++ fieldLabel: gettext('Confirm API Password'), ++ validator: function(value) { ++ var pw = me.up().down('field[name=truenas_secret]').getValue(); ++ if (pw !== value) { ++ return "Secrets do not match!"; ++ } ++ return true; ++ }, ++ }); ++ + me.column1 = [ + { + xtype: me.isCreate ? 'textfield' : 'displayfield', +@@ -58074,7 +58161,7 @@ allowBlank: false, }, { @@ -165,7 +162,7 @@ name: 'pool', value: '', fieldLabel: gettext('Pool'), -@@ -58084,11 +58172,11 @@ +@@ -58084,11 +58171,11 @@ xtype: me.isCreate ? 'textfield' : 'displayfield', name: 'blocksize', value: '4k', @@ -179,7 +176,7 @@ name: 'target', value: '', fieldLabel: gettext('Target'), -@@ -58099,8 +58187,59 @@ +@@ -58099,9 +58186,60 @@ name: 'comstar_tg', value: '', fieldLabel: gettext('Target group'), @@ -188,7 +185,7 @@ + hidden: '{!isComstar}' + }, allowBlank: true, -+}, + }, + { + xtype: 'proxmoxcheckbox', + name: 'freenas_use_ssl', @@ -237,10 +234,11 @@ + bind: { + hidden: '{hideUsername}' + }, - }, ++ }, ]; -@@ -58131,7 +58270,9 @@ + me.column2 = [ +@@ -58131,7 +58269,9 @@ xtype: me.isCreate ? 'textfield' : 'displayfield', name: 'comstar_hg', value: '', @@ -251,19 +249,18 @@ fieldLabel: gettext('Host group'), allowBlank: true, }, -@@ -58139,15 +58280,32 @@ +@@ -58139,15 +58279,32 @@ xtype: me.isCreate ? 'textfield' : 'displayfield', name: 'lio_tpg', value: '', - bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' }, - allowBlank: false, -- fieldLabel: gettext('Target portal group'), + bind: { + hidden: '{!isLIO}' + }, -+ fieldLabel: gettext('Target portal group'), -+ allowBlank: true - }, + fieldLabel: gettext('Target portal group'), ++ allowBlank: true ++ }, + { + xtype: 'proxmoxtextfield', + name: 'freenas_apiv4_host', @@ -275,7 +272,7 @@ + hidden: '{!isFreeNAS}' + }, + fieldLabel: gettext('API IPv4 Host'), -+ }, + }, + tnsecret, + tnconfirmsecret, ]; From ebc461a519c3331b270adda557ccecf08ef94c3f Mon Sep 17 00:00:00 2001 From: Kevin Scott Adams Date: Sun, 7 Jan 2024 09:43:56 -0500 Subject: [PATCH 02/28] Updated README.md - Updated README.md to announce the Bearer Token Authentication feature. --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d6a8a7..51b9c47 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TrueNAS ZFS over iSCSI Plugin for Proxmox VE -## πŸ“’: ATTENTION 2023-08-16 πŸ“’: New repos are now online at [Cloudsmith](#new-installs). +## πŸ“’: ATTENTION 2024-01-07 πŸ“’: Bearer Token Authentication now available in Version 2.3.0 on the testing repo. ## Activity @@ -8,6 +8,15 @@ Expand to see the activity tree
+ +
+ 2024-01-07 + + - Added Bearer Token Authentication. + - Changed variable `freenas_password` to `truenas_secret` to represent either a password or token. + - Identation and whitespace cleanup. + +
2023-08-18 From 636cd06ff61fe8b7b4d43434dbd3a60ea3202fff Mon Sep 17 00:00:00 2001 From: Kevin Adams Date: Fri, 15 May 2026 14:22:04 -0400 Subject: [PATCH 03/28] 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 --- ...DR-001-consolidate-build-into-main-repo.md | 54 +++ .claude/cos/adrs/ADR-002-ui-strategy.md | 79 +++++ .claude/cos/adrs/ADR-003-apt-repo-hosting.md | 69 ++++ .../cos/adrs/ADR-004-cleanup-on-failure.md | 63 ++++ .../ADR-005-bearer-token-authentication.md | 66 ++++ .../cos/adrs/ADR-006-versioning-strategy.md | 59 ++++ .../adrs/ADR-007-freenas-vs-truenas-naming.md | 35 ++ .claude/cos/plans/plan-001-revive-project.md | 171 +++++++++ .claude/cos/plans/plan-002-code-review.md | 243 +++++++++++++ ...-001-generate-patch-for-new-pve-version.md | 70 ++++ .github/FUNDING.yml | 4 +- .github/ISSUE_TEMPLATE/bug_report.md | 59 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 35 ++ .github/PULL_REQUEST_TEMPLATE.md | 32 ++ .github/stale.yml | 24 +- .github/workflows/action.yml | 21 +- .github/workflows/build.yml | 318 +++++++++++++++++ .perlcriticrc | 34 ++ CHANGELOG.md | 67 ++++ CLAUDE.md | 83 +++++ CONTRIBUTING.md | 141 ++++++++ DONORS.md | 32 ++ LICENSE | 251 +++++++++++++- README.md | 328 ++++++++---------- SECURITY.md | 31 ++ packaging/DEBIAN/control.j2 | 22 ++ packaging/DEBIAN/postinst | 156 +++++++++ packaging/DEBIAN/postrm | 105 ++++++ packaging/DEBIAN/triggers | 7 + perl5/PVE/Storage/LunCmd/FreeNAS.pm | 4 +- stable-8/perl5/PVE/Storage/ZFSPlugin.pm.patch | 13 +- 31 files changed, 2450 insertions(+), 226 deletions(-) create mode 100644 .claude/cos/adrs/ADR-001-consolidate-build-into-main-repo.md create mode 100644 .claude/cos/adrs/ADR-002-ui-strategy.md create mode 100644 .claude/cos/adrs/ADR-003-apt-repo-hosting.md create mode 100644 .claude/cos/adrs/ADR-004-cleanup-on-failure.md create mode 100644 .claude/cos/adrs/ADR-005-bearer-token-authentication.md create mode 100644 .claude/cos/adrs/ADR-006-versioning-strategy.md create mode 100644 .claude/cos/adrs/ADR-007-freenas-vs-truenas-naming.md create mode 100644 .claude/cos/plans/plan-001-revive-project.md create mode 100644 .claude/cos/plans/plan-002-code-review.md create mode 100644 .claude/cos/runbooks/runbook-001-generate-patch-for-new-pve-version.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/build.yml create mode 100644 .perlcriticrc create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 CONTRIBUTING.md create mode 100644 DONORS.md create mode 100644 SECURITY.md create mode 100644 packaging/DEBIAN/control.j2 create mode 100644 packaging/DEBIAN/postinst create mode 100644 packaging/DEBIAN/postrm create mode 100644 packaging/DEBIAN/triggers diff --git a/.claude/cos/adrs/ADR-001-consolidate-build-into-main-repo.md b/.claude/cos/adrs/ADR-001-consolidate-build-into-main-repo.md new file mode 100644 index 0000000..06db745 --- /dev/null +++ b/.claude/cos/adrs/ADR-001-consolidate-build-into-main-repo.md @@ -0,0 +1,54 @@ +# ADR-001: Consolidate Build Pipeline into Main Repo + +**Date**: 2026-05-15 +**Status**: Proposed +**Deciders**: Kevin Adams + +## Context + +The build pipeline is split across two GitHub repos: +- `freenas-proxmox` β€” source code + patches +- `freenas-proxmox-packer` β€” DEBIAN packaging structure + CI that actually builds the `.deb` + +Communication is via `repository_dispatch` which requires a stored `ACCESS_TOKEN` secret. This creates maintenance overhead (two repos to keep in sync, two sets of CI secrets, cross-repo dependencies) and confusion about where things live. + +Additionally, the current `postinst` script **git-clones this repo at install time**, which requires internet access on the Proxmox node and is fragile. + +## Decision + +Move all packaging (DEBIAN structure, CI workflow) into the main `freenas-proxmox` repo. The `.deb` package will embed all required files during the build step, not at install time. + +## Consequences + +**Positive**: +- Single repo to maintain +- No cross-repo dispatch tokens needed +- Package installs offline (no git at install time) +- Simpler CI secrets (only Cloudsmith API key needed) +- Easier to test packaging changes alongside code changes + +**Negative**: +- The `freenas-proxmox-packer` repo becomes deprecated (should be archived, not deleted β€” it has history) +- Need to restructure CI branch logic (currently done in the packer repo's action) + +## Implementation Notes + +Proposed directory layout in main repo: +``` +packaging/ +β”œβ”€β”€ DEBIAN/ +β”‚ β”œβ”€β”€ control.j2 # Jinja2/envsubst template for version injection +β”‚ β”œβ”€β”€ postinst +β”‚ β”œβ”€β”€ postrm +β”‚ └── triggers +└── files/ # Files to be embedded in the package (no git clone at install) + └── (populated by CI from the source tree) +``` + +The CI workflow should: +1. Check out the repo +2. Detect branch/tag to set version + repo component (dev/testing/stable) +3. Run `envsubst` on `control.j2` to inject version +4. Copy source files into the package staging area +5. Run `dpkg-deb --build` +6. Push to Cloudsmith (and optionally GitHub Releases) diff --git a/.claude/cos/adrs/ADR-002-ui-strategy.md b/.claude/cos/adrs/ADR-002-ui-strategy.md new file mode 100644 index 0000000..5f080cc --- /dev/null +++ b/.claude/cos/adrs/ADR-002-ui-strategy.md @@ -0,0 +1,79 @@ +# ADR-002: UI Integration Strategy + +**Date**: 2026-05-15 +**Status**: Under Discussion +**Deciders**: Kevin Adams + +## Context + +The plugin currently adds a UI by patching `pvemanagerlib.js`, the monolithic JavaScript bundle that is Proxmox VE's entire web UI. A separate versioned patch file must be maintained for each Proxmox VE minor release. When PVE updates, the patch breaks. + +The desired end state is a UI that: +- Doesn't break on every PVE update +- Ideally doesn't require patching PVE system files +- Shows appropriate fields for TrueNAS API credentials + +## Options Evaluated + +### Option A β€” Continue Patching pvemanagerlib.js (Status Quo) + +The current approach. Diff the upstream PVE JS, produce a patch per PVE version. + +- **Pro**: Works in all PVE versions, field placement is ideal +- **Con**: Breaks on every PVE minor release; versioned patch sprawl is visible in the repo already (stable-5 through stable-8 folders) +- **Verdict**: Manageable with better automation (auto-detect PVE version in postinst and select the right patch) + +### Option B β€” Serve a Separate JS File via pveproxy + +Proxmox VE's `pveproxy` serves everything from `/usr/share/pve-manager/`. The HTML template (`/usr/share/pve-manager/index.html.tpl`) explicitly lists which JS files to load. A new file could be injected either by: +(a) Patching `index.html.tpl` to add a `