Developing Packages¶
Developers familiar with FreeBSD, pkg, and PHP, will find it fairly straightforward to create packages for pfSense® software. End users and organizations can benefit from developing a package that does not exist.
To submit a new package, create a pull request on GitHub for the pfSense software FreeBSD-Ports Repository so Netgate can evaluate the work for inclusion into the package repository for access by all users.
When developing packages, always target the latest development version of pfSense software first.
pfSense Software Package System¶
On pfSense software, every pfSense software package is also a FreeBSD port.
These are installed and managed via pkg
, even when using the GUI to add or
remove packages. Binary packages from FreeBSD are added as dependencies of the
pfSense software package, so they are installed automatically as well, along
with any of their own required dependencies.
The basic idea is to make packages for pfSense software similar to FreeBSD packages, but with customization. One way this is achieved is by adding metadata about packages to the firewall configuration when it is installed, and also creating the configuration screen of an application using XML. pfSense software provides an optional framework to create the web interface and to store it in the XML configuration file of the firewall. The package writer is expected to convert the data from XML to the native format of the application.
Package System¶
pfSense software packages are typically composed of:
A Manifest File
Package configuration file(s)
Supporting files (
.inc
files, additional.php
web interface files, etc.)
See also
See Package Port Directory Structure for a more in-depth list of files and their locations in the package structure.
Manifest File¶
The manifest file is located inside the package’s port directory:
<category>/pfSense-pkg-<package name>/files/usr/local/share/pfSense-pkg-<package name>/info.xml
For example:
sysutils/pfSense-pkg-Cron/files/usr/local/share/pfSense-pkg-Cron/info.xml
This file contains basic information about the package. The format of the manifest XML file is as follows:
<pfsensepkgs>
<package>
<name>someprogram</name>
<descr><![CDATA[Some cool program.]]></descr>
<version>%%PKGVERSION%%</version>
<configurationfile>someprogram.xml</configurationfile>
</package>
</pfsensepkgs>
Note
The version
entry is updated automatically, use the template as shown.
Package Configuration Files¶
The manifest specifies a Package Configuration File for the package using the
config_file
tag. The convention is to keep this file inside the
files/usr/local/pkg/
directory inside the port structure for the package.
The easiest way to get a feel for the format is to look at existing packages and how they use these configuration files, how their fields look, how the code behaves, and so on.
The format is:
<?xml version="1.0" encoding="utf-8" ?>
<packagegui>
<copyright></copyright>
<name></name>
<title></title>
<include_file></include_file>
<aftersaveredirect></aftersaveredirect>
<menu>
<name></name>
<section></section>
<configfile></configfile>
<tooltiptext></tooltiptext>
<url>/pkg.php?xml=package.xml</url>
</menu>
<tabs>
<tab>
<text></text>
<url></url>
<active/>
<tab_level/>
</tab>
</tabs>
<service>
<name></name>
<rcfile></rcfile>
<executable></executable>
<description></description>
</service>
<plugins>
<item>
<type>plugin_name</type>
<item>
</plugins>
<adddeleteeditpagefields>
<columnitem>
<fielddescr></fielddescr>
<fieldname></fieldname>
</columnitem>
</adddeleteeditpagefields>
<fields>
<field>
<fielddescr></fielddescr>
<fieldname></fieldname>
<description></description>
<size></size>
<type></type>
</field>
</fields>
<custom_php_global_functions><!-- PHP function call --></custom_php_global_functions>
<custom_php_install_command><!-- PHP function call --></custom_php_install_command>
<custom_php_pre_deinstall_command><!-- PHP function call --></custom_php_pre_deinstall_command>
<custom_php_deinstall_command><!-- PHP function call --></custom_php_deinstall_command>
<custom_add_php_command><!-- PHP function call --></custom_add_php_command>
<custom_add_php_command_late><!-- PHP function call --></custom_add_php_command_late>
<custom_delete_php_command><!-- PHP function call --></custom_delete_php_command>
<custom_php_resync_config_command><!-- PHP function call --></custom_php_resync_config_command>
<start_command><!-- PHP function call --></start_command>
<custom_php_service_status_command><!-- PHP function call --></custom_php_service_status_command>
<custom_php_validation_command><!-- PHP function call --></custom_php_validation_command>
<custom_php_after_head_command><!-- PHP function call --></custom_php_after_head_command>
<custom_php_command_before_form><!-- PHP function call --></custom_php_command_before_form>
<custom_php_after_form_command><!-- PHP function call --></custom_php_after_form_command>
</packagegui>
Field types¶
interfaces_selection
Combo/list box with interfaces list:
<field> <fielddescr>Interface Selection</fielddescr> <fieldname>interfaces</fieldname> <type>interfaces_selection</type> <description>Select interfaces to listen on</description> <multiple/><!-- (optional) --> <size>10</size><!-- (optional) --> <hideinterfaceregex>(wan|loopback)</hideinterfaceregex><!-- (optional) --> <showvirtualips/><!-- (optional) --> <showips/><!-- (optional) --> <showlistenall/><!-- (optional) --> </field>
checkbox
Field with text description and a enable/disable checkbox:
<field> <fielddescr>Enable</fielddescr> <fieldname>enable_package</fieldname> <type>checkbox</type> <description>Select this option to enable this config</description> </field>
input
Single line text edit element
<field> <fielddescr>username</fielddescr> <fieldname>username</fieldname> <type>input</type> <description>Enter package username</description> </field>
password
Special input element for passwords, all input will be masked with
*
symbol on GUI but clear text on xml config file:<field> <fielddescr>password</fielddescr> <fieldname>password</fieldname> <type>password</type> <description>Enter password</description> </field>
textarea
Multi-line text edit element:
<field> <fielddescr>Custom options</fielddescr> <fieldname>custom_options</fieldname> <type>textarea</type> <description>Paste custom config here</description> <encoding>base64</encoding><!-- (optional) --> </field>
select
Combo Box with drop-down list items:
<field> <fielddescr>Some Choice</fielddescr> <fieldname>some_choice</fieldname> <description><![CDATA[Select a choice]]></description> <type>select</type> <options> <option><name>Choice A</name><value>a</value></option> <option><name>Choice B</name><value>b</value></option> </options> <multiple/><!-- (optional) --> <size>10</size><!-- (optional) --> </field>
info
Information text without any options to select:
<field> <fielddescr>Additional info</fielddescr> <fieldname>just_info</fieldname> <type>info</type> <description>show info text on package GUI</description> </field>
button
Additional buttons to take additional actions on packages:
<field> <fielddescr>Reload config</fielddescr> <fieldname>reload</fieldname> <type>button</type> <description>click to force a config reload</description> <placeonbottom/><!-- Use this option to place the button besides save default button --> </field>
In package
.inc
file, to check what button was selected, use:if (($_POST['Submit'] == 'Save') { /* Do save stuff */ } if (($_POST['Submit'] == 'Reload') || !isset($_POST['Submit'])) { /* Do reload stuff */ }
Field groups¶
combinefields
Several options can be combined into a single row as an option group using
<combinefields>
For example this block groups three options onto a single row:
<field> <fielddescr>Option 1</fielddescr> <fieldname>option1</fieldname> <description>Option 1</description> <type>input</type> <combinefields>begin</combinefields> </field> <field> <fielddescr>Option 2</fielddescr> <fieldname>option1</fieldname> <description>Option 2</description> <type>input</type> <field> <fielddescr>Option 3</fielddescr> <fieldname>option3</fieldname> <description>Option 3</description> <type>input</type> <combinefields>end</combinefields> </field>
rowhelper
Used in
pkg_edit.php
to add multiple config lines like a table on package GUI. Inside rowhelper, add any field type described above:<field> <fielddescr><![CDATA[Lists]]></fielddescr> <fieldname>none</fieldname> <description><![CDATA['Format' - Choose the file format that url will retrieve or local file format.]]></description> <type>rowhelper</type> <rowhelper> <rowhelperfield> <fielddescr>Format</fielddescr> <fieldname>format</fieldname> <type>select</type> <options> <option><name>gz</name><value>gz</value></option> <option><name>txt</name><value>txt</value></option> </options> </rowhelperfield> <rowhelperfield> <fielddescr>URL or local file</fielddescr> <fieldname>url</fieldname> <type>input</type> <size>75</size> </rowhelperfield> </rowhelper> </field>
adddeleteeditpagefields
Used with
pkg.php
to have multiple config of the same xml page. Useful to access lists, users lists, multi daemon configurations, etc:<adddeleteeditpagefields> <columnitem> <fielddescr>Alias</fielddescr> <fieldname>aliasname</fieldname> </columnitem> <columnitem> <fielddescr>Description</fielddescr> <fieldname>description</fieldname> </columnitem> <columnitem> <fielddescr>Action</fielddescr> <fieldname>action</fieldname> </columnitem> <columnitem> <fielddescr>Update Frequency</fielddescr> <fieldname>cron</fieldname> </columnitem> </adddeleteeditpagefields>
Binaries from FreeBSD¶
The actual binaries are normal FreeBSD package binaries for that particular program. Once listed as a dependency for a pfSense package in its Makefile, the builders compile the dependencies automatically and copy them to the pfSense software package servers. There is no need to specify these in XML.
Updating Packages¶
When updating a package is it important to bump the version in its Makefile otherwise the package will not be rebuilt and made available to others.
Repository Branches¶
When submitting changes, they are typically submitted to the devel
branch of
the FreeBSD-Ports Repository. In order to show to all users, the changes must
be placed in the current release branch as well, such as
RELENG_2_7_2.
Ideally, the changes should be submitted to the development branches and tested on systems pulling packages from the development repository. Once the changes have been tested, they can be placed into the release branch for deployment to a wider audience.
Testing/Building Individual Packages¶
Archive files from pkg
may be copied to the firewall and added with pkg
directly. The good thing about using pkg
is that the GUI packages and CLI
packages are all the same. Files for pfSense software packages are all kept
together inside the archive, dependencies such as FreeBSD packages are in
separate archives.
The package may be compiled on a local FreeBSD 14.0-CURRENT@0c783a37d5d5
builder, then pkg delete
the old version and then pkg add
the new one or
use any other pkg
operations needed.
For example, a basic package like Cron
is pfSense-pkg-Cron-0.3.3
, so if
a new copy is built and put on the firewall:
# pkg add /path/to/file/pfSense-pkg-Cron-0.3.3.txz
It will also work with pkg add
and a URL to an http
or https
web
server.
The process for making a package is:
Check out the FreeBSD-Ports Repository.
Make changes
Run
make package
:$ git clone git@github.com:pfsense/FreeBSD-ports.git pfSense-ports $ cd pfSense-ports/blah/pfSense-pkg-foo/ [hack, hack, hack] $ make package (might need sudo) $ scp work/pkg/pfSense-pkg-foo* root@myfirewall:.
And then on the firewall:
# pkg add pfSense-pkg-foo-<version>.txz
Poudriere could also be setup for a custom repository but in most cases that will be overkill.
There are additional considerations when adding files, like updating the plist, and crafting a new pfSense package from scratch may be tricky if there is no prior knowledge of how the FreeBSD ports tree works.