DIY Plugin Development

    • 3rd-party
    • DIY Plugin Development

      Description
      These Guides are from @SVS.
      @SVS tested this code but just in case:
      USE AT OWN RISK. THIS MAY CORRUPT YOUR SYSTEM AND YOU CAN LOSE DATA.

      This tutorial aims to give you some knowledge how to make plugins for OMV 0.5.xx.
      I recommend to do development on a virtual system.
      I recommend that you have knowledge of the following: javascript, php, shell scripts

      • Part 1: GUI (Web interface)
      • Part 2: config.xml and RPC
      • Part 3: Modules and Shell Scripts
      OMV stoneburner | HP Microserver | 256GB Samsung 830 SSD for system | 4x 2TB in a RAID5
      OMV erasmus| Odroid XU4 | 5TB Data drive | 500GB Backup drive

      The post was edited 2 times, last by WastlJ ().

    • Part 1: GUI (Web interface)

      The GUI base system (Ext JS 4.2.x) uses JavaScript files to construct the graphical view of the pages.

      Check out the OMV types at docs.openmediavault.org/group__webgui.html
      Check out the full Ext JS API at docs.sencha.com/extjs/4.2.0/#!/api

      The backend automatically scans the following folders (and their subfolders) for JavaScript files (.js).

      Source Code

      1. /var/www/openmediavault/js/omv/module/admin/
      2. /var/www/openmediavault/js/omv/module/user/
      3. /var/www/openmediavault/js/omv/module/public/

      Maybe you already guessed that the stuff in the admin folder is only available to the admin and stuff in the user folder is available to the user (Yes, users can also login to the web interface). So if you are making a plugin that is intended for users then put it in the users folder.


      STEP 1: Start by making a separate folder for your own plugin.


      Source Code

      1. /var/www/openmediavault/js/omv/module/admin/service/example



      STEP 2: A node is a single object in the navigation view (folder/tree view on the left).

      Each node is declared in a separate JavaScript file. Now make a file called Example.js with the following code:

      JavaScript Source Code

      1. // Register a node in the navigation tree.
      2. //
      3. // id:
      4. // Set the ID of the node.
      5. // path:
      6. // Parent path in the navigation view.
      7. // Text:
      8. // Service name/title. This is displayed in the navigation.
      9. // icon16:
      10. // 16x16 pixel icon that is displayed in the navigation tree.
      11. // iconSvg:
      12. // SVG icon that is displayed in the navigation view.
      13. OMV.WorkspaceManager.registerNode({
      14. id: 'example',
      15. path: '/service',
      16. text: _('Example'),
      17. icon16: 'images/example.png',
      18. iconSvg: 'images/example.svg'
      19. });
      Display All

      Place the file in the folder you created in step one and go to the GUI to see if it showed up. If you already are in the GUI refresh the page to update the changes. Under Services folder you should now have an ftp icon with the text “Example” next to it.

      With never versions of OpenMediaVault you need to purge the internal cache of Javascript files. This can be achieved by running source /usr/share/openmediavault/scripts/helper-functions && omv_purge_internal_cache on the server.

      A quick note about translations. As you can see above the text option is wrapped in _(). _ is a function that translates text and takes a string as first parameter. This means that text that you want translated in the future should be wrapped in _() and text that you don't, you just write as normally.


      STEP 3: A node has “main view” that is called a workspace.


      There are different class types derived from the workspace class. In this example we will extend the class OMV.workspace.form.Panel. Couple of features of this class:
      • It has built in Save and Reset buttons.
      • It can be used as a tab.
      Make a file called Settings.js and enter the following code into it. Read the comments and try to understand what's happening.

      JavaScript Source Code

      1. Ext.define('OMV.module.admin.service.example.Settings', {
      2. extend: 'OMV.workspace.form.Panel',
      3. // This path tells which RPC module and methods this panel will call to get
      4. // and fetch its form values.
      5. rpcService: 'Example',
      6. rpcGetMethod: 'getSettings',
      7. rpcSetMethod: 'setSettings',
      8. // getFormItems is a method which is automatically called in the
      9. // instantiation of the panel. This method returns all fields for
      10. // the panel.
      11. getFormItems: function() {
      12. return [{
      13. // xtype defines the type of this entry. Some different types
      14. // is: fieldset, checkbox, textfield and numberfield.
      15. xtype: 'fieldset',
      16. title: _('General'),
      17. fieldDefaults: {
      18. labelSeparator: ''
      19. },
      20. // The items array contains items inside the fieldset xtype.
      21. items: [{
      22. xtype: 'checkbox',
      23. // The name option is sent together with is value to RPC
      24. // and is also used when fetching from the RPC.
      25. name: 'enable',
      26. fieldLabel: _('Enable'),
      27. // checked sets the default value of a checkbox.
      28. checked: false
      29. },
      30. {
      31. xtype: 'numberfield',
      32. name: 'max_value',
      33. fieldLabel: _('Max value'),
      34. minValue: 0,
      35. maxValue: 100,
      36. allowDecimals: false,
      37. allowBlank: true
      38. }]
      39. }];
      40. }
      41. });
      42. // Register a panel into the GUI.
      43. //
      44. // path:
      45. // We want to add the panel in our example node.
      46. // The node was configured with the path /service and the id example.
      47. // The path is therefore /service/example.
      48. //
      49. // className:
      50. // The panel which should be registered and added (refers to
      51. // the class name).
      52. OMV.WorkspaceManager.registerPanel({
      53. id: 'settings',
      54. path: '/service/example',
      55. text: _('Settings'),
      56. position: 10,
      57. className: 'OMV.module.admin.service.example.Settings'
      58. });
      Display All

      Now put this in the same folder as the Example.js.


      STEP 4: In order to make the GUI error free we need to make an RPC Service for the form.

      This is not explained as it is part of the next tutorial. Create a file called example.inc and write the following code into the file.

      PHP Source Code

      1. <?php
      2. require_once 'openmediavault/config.inc';
      3. require_once 'openmediavault/error.inc';
      4. require_once 'openmediavault/notify.inc';
      5. require_once 'openmediavault/rpcservice.inc';
      6. class OMVRpcServiceExample extends OMVRpcServiceAbstract
      7. {
      8. /**
      9. * Get the name of the RPC service.
      10. *
      11. * @return string
      12. */
      13. public function getName()
      14. {
      15. return 'Example';
      16. }
      17. /**
      18. * Initialize the RPC service. The RPC methods are registered in this
      19. * function with $this->registerMethod.
      20. *
      21. * @return void
      22. */
      23. public function initialize()
      24. {
      25. $this->registerMethod('getSettings');
      26. $this->registerMethod('setSettings');
      27. }
      28. public function getSettings($params, $context)
      29. {
      30. // Not implemented.
      31. }
      32. public function setSettings($params, $context)
      33. {
      34. // Not implemented.
      35. }
      36. }
      37. // Register the RPC service.
      38. $rpcServiceMgr = &OMVRpcServiceMgr::getInstance();
      39. $rpcServiceMgr->registerService(new OMVRpcServiceExample());
      Display All

      Put the file in the following folder /usr/share/openmediavault/engined/rpc. Then open the terminal and run the following command: service openmediavault-engined restart.

      That's it for the RPC for now. We will continue to work on it in the next tutorial.

      Go the the web interface and click on your service. Now you should see the a view with the Save and Reset buttons on top, a fieldset with the title "General" that contains a check box and field where you can enter only numbers.

      You could now try to make changes to the layout. For example, try adding a text field (hint, xtype: “textfield”).

      If you get an error message “RPC service not found (name=Example)” there is something wrong with the example.inc file. Go through STEP 4 again.

      Useful Links:
      docs.openmediavault.org/group__webgui.html
      docs.sencha.com/extjs/4.2.0/#!/api
      jsfiddle.net/ (Useful for checking errors in your JavaScript code. Apply the ExtJS library from the top left and click JSHint to validate code)

      The post was edited 7 times, last by HK-47 ().

    • Part 2: config.xml and RPC

      In order to store the settings of your plugin a file called config.xml has to modified. The config.xml file contains all parameters that the web GUI holds. The structure is similar to the www folder structure. The configuration file is located in /etc/openmediavault/config.xml. There's also a template located in /usr/share/openmediavault/templates/config.xml.

      First you should make a copy of your config.xml just in case you make an error in your script. Do this by running cp /etc/openmediavault/config.xml /etc/openmediavault/config.xml.backup. If you need to restore the backup run cp /etc/openmediavault/config.xml.backup /etc/openmediavault/config.xml.

      The easiest way to insert your plugin parameters in the configuration file is to make a shell script. Shell scripts are also used in the Debian packages. This means that if you're making an actual plugin there is no point using anything else than a shell script. OpenMediaVault has helper functions that makes it easier to set the initial configuration for a plugin.

      Start by creating a new file called postinst. Insert the following code in to the file.

      Shell-Script

      1. #!/bin/sh
      2. set -e
      3. . /etc/default/openmediavault
      4. . /usr/share/openmediavault/scripts/helper-functions
      5. case "$1" in
      6. configure)
      7. SERVICE_XPATH_NAME="example"
      8. SERVICE_XPATH="/config/services/${SERVICE_XPATH_NAME}"
      9. # Add the default configuration
      10. if ! omv_config_exists "${SERVICE_XPATH}"; then
      11. omv_config_add_element "/config/services" "${SERVICE_XPATH_NAME}"
      12. omv_config_add_element "${SERVICE_XPATH}" "enable" "0"
      13. omv_config_add_element "${SERVICE_XPATH}" "max_value" "0"
      14. fi
      15. # Activate package triggers. These triggers are only set during the
      16. # package installation.
      17. dpkg-trigger update-fixperms
      18. dpkg-trigger update-locale
      19. ;;
      20. abort-upgrade|abort-remove|abort-deconfigure)
      21. ;;
      22. *)
      23. echo "postinst called with unknown argument" >&2
      24. exit 1
      25. ;;
      26. esac
      27. #DEBHELPER#
      28. exit 0
      Display All

      This script normally has to be run as root with the following command /bin/sh postinst configure

      You should also make a file called postrm. This is used to clear the entries if the user purges the plugin. This is also useful if you make a mistake. You can delete the entries to the config.xml with the following script.

      Shell-Script

      1. #!/bin/sh
      2. set -e
      3. . /etc/default/openmediavault
      4. . /usr/share/openmediavault/scripts/helper-functions
      5. SERVICE_XPATH_NAME="example"
      6. SERVICE_XPATH="/config/services/${SERVICE_XPATH_NAME}"
      7. case "$1" in
      8. purge)
      9. if omv_config_exists ${SERVICE_XPATH}; then
      10. omv_config_delete ${SERVICE_XPATH}
      11. fi
      12. ;;
      13. remove)
      14. ;;
      15. upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
      16. ;;
      17. *)
      18. echo "postrm called with unknown argument \`$1'" >&2
      19. exit 1
      20. ;;
      21. esac
      22. #DEBHELPER#
      23. exit 0
      Display All

      Now you can delete your configurations with the following command /bin/sh postrm purge.


      The RPC = Remote Procedure Call


      It should be noted that this guide uses the PSR-2 standard for PHP code. The PSR-2 standard is used by the OpenMediaVault Plugin Developers and the specification can be found here. The official OpenMediaVault code and plugins is written with the guidelines stated here. As usual, write all code so that it looks like only one developer has written the code. The more alike the plugins are written the easier it is to understand how plugins work and contribute to them.

      On the server side there are mulitple files ending with .inc, those are PHP files. The RPC files are the link between the web GUI and config.xml. The RPC files are located in the following folder /usr/share/openmediavault/engined/rpc.

      In the first part of the tutorial we created the RPC file called example.inc. There are a couple of empty functions (getSettings and setSettings) in that file that needs to be completed. Below is the complete code for the RPC file. Check the comments for more information.

      PHP Source Code

      1. <?php
      2. require_once 'openmediavault/config.inc';
      3. require_once 'openmediavault/error.inc';
      4. require_once 'openmediavault/notify.inc';
      5. require_once 'openmediavault/rpcservice.inc';
      6. class OMVRpcServiceExample extends OMVRpcServiceAbstract
      7. {
      8. /**
      9. * The main event message path.
      10. *
      11. * @var string
      12. */
      13. private $eventMessagePath = 'org.openmediavault.services.example';
      14. /**
      15. * Get the base XPath of the service. This is a helper function to avoid
      16. * "magic numbers".
      17. *
      18. * @return string
      19. */
      20. private function getXpath()
      21. {
      22. return '/config/services/example';
      23. }
      24. /**
      25. * Get the name of the RPC service.
      26. *
      27. * @return string
      28. */
      29. public function getName()
      30. {
      31. return 'Example';
      32. }
      33. /**
      34. * Initialize the RPC service. The RPC methods are registered in this
      35. * function with $this->registerMethod.
      36. *
      37. * @return void
      38. */
      39. public function initialize()
      40. {
      41. $this->registerMethod('getSettings');
      42. $this->registerMethod('setSettings');
      43. }
      44. public function getSettings($params, $context)
      45. {
      46. // $xmlConfig is needed when reading and writing from the configuration.
      47. global $xmlConfig;
      48. // Validate the RPC caller context.
      49. //
      50. // validateMethodContext takes the currentcontext as the first
      51. // parameter. The second paramter is the valid context and that can be
      52. // OMV_ROLE_ADMINISTRATOR, OMV_ROLE_USER or OMV_ROLE_EVERYONE.
      53. // This is used to make sure that the right user accesses the method.
      54. $this->validateMethodContext($context, ['role' => OMV_ROLE_ADMINISTRATOR]);
      55. // Get the configuration object.
      56. $object = $xmlConfig->get($this->getXpath());
      57. // If no data was found, throw an exception and provide the XPath that
      58. // failed.
      59. if (is_null($object)) {
      60. throw new OMVException(
      61. OMVErrorMsg::E_CONFIG_GET_OBJECT_FAILED,
      62. $this->getXpath()
      63. );
      64. }
      65. // Modify the result data.
      66. // boolval and intval converts strings and numbers to their boolean
      67. // and integer value.
      68. $object['enable'] = boolval($object['enable']);
      69. $object['max_value'] = intval($object['max_value']);
      70. return $object;
      71. }
      72. public function setSettings($params, $context)
      73. {
      74. global $xmlConfig;
      75. $this->validateMethodContext($context, array(
      76. "role" => OMV_ROLE_ADMINISTRATOR
      77. ));
      78. // Validate the parameters of the RPC service method.
      79. //
      80. // OpenMediavault uses JSON Schema to validate parameters. A more
      81. // detailed specification is provided here http://json-schema.org/
      82. $this->validateMethodParams(
      83. $params,
      84. '{
      85. "type": "object",
      86. "properties": {
      87. "enable": {
      88. "type": "boolean"
      89. },
      90. "max_value":{
      91. "type": "integer",
      92. "minimum": 1,
      93. "maximum": 100
      94. }
      95. }
      96. }'
      97. );
      98. // Update the configuration object.
      99. $object = [
      100. 'enable' => boolval($params['enable']),
      101. 'max_value' => $params['max_value'],
      102. ];
      103. // Update the configuration file. If it fails it throws an exception.
      104. if ($xmlConfig->replace($this->getXpath(), $object) === false) {
      105. throw new OMVException(
      106. OMVErrorMsg::E_CONFIG_SET_OBJECT_FAILED,
      107. $this->getXpath()
      108. );
      109. }
      110. // Notify configuration changes.
      111. //
      112. // This will notify event listeners such as the service module
      113. // to perform certain tasks. The most common one is to mark the
      114. // service as dirty.
      115. $dispatcher = &OMVNotifyDispatcher::getInstance();
      116. $dispatcher->notify(OMV_NOTIFY_MODIFY, $this->eventMessagePath, $object);
      117. return $object;
      118. }
      119. }
      120. // Register the RPC service.
      121. $rpcServiceMgr = &OMVRpcServiceMgr::getInstance();
      122. $rpcServiceMgr->registerService(new OMVRpcServiceExample());
      Display All

      After you have move the new version of the RPC file to the correct folder you have to restart the omv-engined with the following command in the terminal: service openmediavault-engined restart.

      Now you can test it out by modifying your settings in the web interface and saving them. The settings should now stay in the configuration if you click the Save button at the top of the screen. If you have problems with the settings not being stored then double check that your shell script and RPC have same variable names.
      OMV stoneburner | HP Microserver | 256GB Samsung 830 SSD for system | 4x 2TB in a RAID5
      OMV erasmus| Odroid XU4 | 5TB Data drive | 500GB Backup drive

      The post was edited 11 times, last by HK-47 ().

    • Part 3: Modules and Shell Scripts

      Modules are used to monitor and execute changes made to your plugins configuration. Modules start and stop services (like SSH, FTP etc.) and they also update the status (running/stopped) of the service in the web interface. If your plugin does not control any services then you can execute the shell scripts from the RPC (see how to execute a shell script from the example module below). As the previous part of the tutorial said, files ending with .inc are actually PHP files. The same applies for modules. Modules are located in the following folder /usr/share/openmediavault/engined/module.

      To create a module start by creating a file called example.inc. Put the following code in the file and check out the comments for more info.

      PHP Source Code

      1. <?php
      2. require_once 'openmediavault/config.inc';
      3. require_once 'openmediavault/error.inc';
      4. require_once 'openmediavault/initscript.inc';
      5. require_once 'openmediavault/module.inc';
      6. class OMVModuleExample extends OMVModuleServiceAbstract implements
      7. OMVINotifyListener,
      8. OMVIModuleServiceStatus
      9. {
      10. /**
      11. * The main event message path.
      12. *
      13. * @var string
      14. */
      15. private $eventMessagePath = 'org.openmediavault.services.example';
      16. }
      17. /**
      18. * Get the base XPath of the service. This is a helper function to avoid
      19. * "magic numbers".
      20. *
      21. * @return string
      22. */
      23. private function getXpath()
      24. {
      25. return '/config/services/example';
      26. }
      27. /**
      28. * Get the module name.
      29. *
      30. * @return string
      31. */
      32. public function getName()
      33. {
      34. return 'example';
      35. }
      36. /**
      37. * Get the module status.
      38. *
      39. * @return array
      40. *
      41. * @throws OMVException
      42. */
      43. public function getStatus()
      44. {
      45. global $xmlConfig;
      46. // Get the configuration object.
      47. $object = $xmlConfig->get($this->getXpath());
      48. if (is_null($object)) {
      49. throw new OMVException(
      50. OMVErrorMsg::E_CONFIG_GET_OBJECT_FAILED,
      51. $this->getXpath()
      52. );
      53. }
      54. // Return the status of the service. This information is displayed
      55. // under Diagnostics/Services.
      56. return array(
      57. 'name' => $this->getName(),
      58. 'title' => gettext('Example'),
      59. 'enabled' => boolval($object['enable']),
      60. 'running' => false
      61. );
      62. }
      63. /**
      64. * Generate the configuration.
      65. *
      66. * @return void
      67. *
      68. * @throws OMVException
      69. */
      70. public function applyConfig()
      71. {
      72. global $xmlConfig;
      73. $cmd = sprintf('export LANG=C; omv-mkconf %s 2>&1', $this->getName());
      74. if (0 !== $this->exec($cmd, $output)) {
      75. throw new OMVException(
      76. OMVErrorMsg::E_EXEC_FAILED,
      77. $cmd,
      78. implode(PHP_EOL, $output)
      79. );
      80. }
      81. }
      82. /**
      83. * Bind listeners.
      84. *
      85. * @param OMVNotifyDispatcher $dispatcher
      86. * @return void
      87. */
      88. public function bindListeners(OMVNotifyDispatcher $dispatcher)
      89. {
      90. $moduleMgr = &OMVModuleMgr::getInstance();
      91. // Add listeners here. The most common thing is to monitor configuration
      92. // changes on the service. When the config is changed the module
      93. // sets itself as dirty (as seen below). Setting a module as dirty
      94. // makes the apply button appear in the web interface (which in turn
      95. // calls the applyConfig function on each module with a dirty state).
      96. $dispatcher->addListener(
      97. OMV_NOTIFY_MODIFY,
      98. $this->eventMessagePath,
      99. [$this, 'setDirty']
      100. );
      101. }
      102. }
      103. // Register the module.
      104. $moduleMgr = &OMVModuleMgr::getInstance();
      105. $moduleMgr->registerModule(new OMVModuleExample());
      Display All

      Put the file in to the module folder /usr/share/openmediavault/engined/module. In order for the module to load, restart omv-engined with service openmediavault-engined restart.


      Generating the configuration with shell scripts


      Shell scripts are the final link for your plugin. They are needed to update the configuration files of the actual software that the plugin controls. When configuration changes are applied, the module executes the shell command omv-mkconf example (this does depend on what your module contains though). Before this works we have to create the shell script example.

      The following script reads the settings from the config.xml and writes to a file. As you can see we are using the helper-functions that OMV provides. Check the helper-functions file for even more functions.

      Shell-Script

      1. #!/bin/sh
      2. set -e
      3. . /etc/default/openmediavault
      4. . /usr/share/openmediavault/scripts/helper-functions
      5. OMV_EXAMPLE_XPATH="/config/services/example"
      6. OMV_EXAMPLE_CONF="/tmp/example.conf"
      7. cat <<EOF > ${OMV_EXAMPLE_CONF}
      8. enable = $(omv_config_get "${OMV_EXAMPLE_XPATH}/enable")
      9. max_value = $(omv_config_get "${OMV_EXAMPLE_XPATH}/max_value")
      10. EOF
      11. exit 0
      Display All

      Put the file in the /usr/share/openmediavault/mkconf directory. Remember to check the file permissions for your shell script. You can use the following command to set the correct permissions on the file chmod 755 example.

      When you apply changes the script is called and it writes the information to the file /tmp/example.conf. If the file doesn't exist it is created by the script.

      This is the last part of the tutorial for OMV plugins. Now you should have a basic understanding about the files and code considering plugin development. How to make a Debian package is not included in this tutorial.

      If you want to know how to implement a feature of the OMV core in to your plugin you can always check the original code. You should know where to look.

      Thank you very much @SVS for this 3 part Guide!
      OMV stoneburner | HP Microserver | 256GB Samsung 830 SSD for system | 4x 2TB in a RAID5
      OMV erasmus| Odroid XU4 | 5TB Data drive | 500GB Backup drive

      The post was edited 5 times, last by HK-47 ().