diff --git a/net/haproxy/+POST_DEINSTALL b/net/haproxy/+POST_DEINSTALL
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/net/haproxy/+POST_INSTALL b/net/haproxy/+POST_INSTALL
new file mode 100644
index 0000000000..92d649b8a4
--- /dev/null
+++ b/net/haproxy/+POST_INSTALL
@@ -0,0 +1,4 @@
+echo "restarting configd..."
+if /usr/local/etc/rc.d/configd status > /dev/null; then
+ /usr/local/etc/rc.d/configd restart
+fi
diff --git a/net/haproxy/+PRE_DEINSTALL b/net/haproxy/+PRE_DEINSTALL
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/net/haproxy/+PRE_INSTALL b/net/haproxy/+PRE_INSTALL
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/net/haproxy/Makefile b/net/haproxy/Makefile
new file mode 100644
index 0000000000..da6899bad0
--- /dev/null
+++ b/net/haproxy/Makefile
@@ -0,0 +1,7 @@
+PLUGIN_NAME= haproxy
+PLUGIN_VERSION= 1.0
+PLUGIN_COMMENT= Reliable, high performance TCP/HTTP load balancer
+#PLUGIN_DEPENDS=
+PLUGIN_MAINTAINER= opnsense@moov.de
+
+.include "../../Mk/plugins.mk"
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php
new file mode 100644
index 0000000000..251f8166ce
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/ServiceController.php
@@ -0,0 +1,168 @@
+request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun("haproxy start", true);
+ return array("response" => $response);
+ } else {
+ return array("response" => array());
+ }
+ }
+
+ /**
+ * stop haproxy service
+ * @return array
+ */
+ public function stopAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun("haproxy stop");
+ return array("response" => $response);
+ } else {
+ return array("response" => array());
+ }
+ }
+
+ /**
+ * restart haproxy service
+ * @return array
+ */
+ public function restartAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun("haproxy restart");
+ return array("response" => $response);
+ } else {
+ return array("response" => array());
+ }
+ }
+
+ /**
+ * retrieve status of haproxy service
+ * @return array
+ * @throws \Exception
+ */
+ public function statusAction()
+ {
+ $backend = new Backend();
+ $mdlProxy = new HAProxy();
+ $response = $backend->configdRun("haproxy status");
+
+ if (strpos($response, "not running") > 0) {
+ if ($mdlProxy->general->enabled->__toString() == 1) {
+ $status = "stopped";
+ } else {
+ $status = "disabled";
+ }
+ } elseif (strpos($response, "is running") > 0) {
+ $status = "running";
+ } elseif ($mdlProxy->general->enabled->__toString() == 0) {
+ $status = "disabled";
+ } else {
+ $status = "unkown";
+ }
+
+ return array("status" => $status);
+ }
+
+ /**
+ * reconfigure haproxy, generate config and reload
+ */
+ public function reconfigureAction()
+ {
+ if ($this->request->isPost()) {
+ $force_restart = false;
+ // close session for long running action
+ $this->sessionClose();
+
+ $mdlProxy = new HAProxy();
+ $backend = new Backend();
+
+ $runStatus = $this->statusAction();
+
+ // stop haproxy when disabled
+ if ($runStatus['status'] == "running" &&
+ ($mdlProxy->general->enabled->__toString() == 0 || $force_restart)) {
+ $this->stopAction();
+ }
+
+ // generate template
+ $backend->configdRun("template reload OPNsense.HAProxy");
+
+ // (res)start daemon
+ if ($mdlProxy->general->enabled->__toString() == 1) {
+ if ($runStatus['status'] == "running" && !$force_restart) {
+ $backend->configdRun("haproxy reconfigure");
+ } else {
+ $this->startAction();
+ }
+ }
+
+ return array("status" => "ok");
+ } else {
+ return array("status" => "failed");
+ }
+ }
+
+ /**
+ * run syntax check for haproxy configuration
+ * @return array
+ * @throws \Exception
+ */
+ public function configtestAction()
+ {
+ $backend = new Backend();
+ // first generate template based on current configuration
+ $backend->configdRun("template reload OPNsense.HAProxy");
+ // now run the syntax check
+ $response = $backend->configdRun("haproxy configtest");
+ return array("result" => $response);
+ }
+}
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php
new file mode 100644
index 0000000000..06ee4407ab
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/SettingsController.php
@@ -0,0 +1,1009 @@
+"failed","validations" => array());
+ // perform validation
+ $valMsgs = $mdl->performValidation();
+ foreach ($valMsgs as $field => $msg) {
+ // replace absolute path to attribute for relative one at uuid.
+ if ($node != null) {
+ $fieldnm = str_replace($node->__reference, $reference, $msg->getField());
+ $result["validations"][$fieldnm] = $msg->getMessage();
+ } else {
+ $result["validations"][$msg->getField()] = $msg->getMessage();
+ }
+ }
+
+ // serialize model to config and save when there are no validation errors
+ if (count($result['validations']) == 0) {
+ // save config if validated correctly
+ $mdl->serializeToConfig();
+
+ Config::getInstance()->save();
+ $result = array("result" => "saved");
+ }
+
+ return $result;
+ }
+
+ /**
+ * retrieve haproxy settings
+ * @return array
+ */
+ public function getAction()
+ {
+ $result = array();
+ if ($this->request->isGet()) {
+ $mdlProxy = new HAProxy();
+ $result['haproxy'] = $mdlProxy->getNodes();
+ }
+
+ return $result;
+ }
+
+ /**
+ * update haproxy configuration fields
+ * @return array
+ * @throws \Phalcon\Validation\Exception
+ */
+ public function setAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->hasPost("haproxy")) {
+ // load model and update with provided data
+ $mdlProxy = new HAProxy();
+ $mdlProxy->setNodes($this->request->getPost("haproxy"));
+
+ // perform validation
+ $valMsgs = $mdlProxy->performValidation();
+ foreach ($valMsgs as $field => $msg) {
+ if (!array_key_exists("validations", $result)) {
+ $result["validations"] = array();
+ }
+ $result["validations"]["haproxy.".$msg->getField()] = $msg->getMessage();
+ }
+
+ // serialize model to config and save
+ if ($valMsgs->count() == 0) {
+ $mdlProxy->serializeToConfig();
+ $cnf = Config::getInstance();
+ $cnf->save();
+ $result["result"] = "saved";
+ }
+
+ }
+
+ return $result;
+ }
+
+ /**
+ * retrieve frontend settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getFrontendAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('frontends.frontend.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("frontend" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->frontends->frontend->add() ;
+ return array("frontend" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update frontend with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setFrontendAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("frontend")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('frontends.frontend.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("frontend"));
+ return $this->save($mdlCP, $node, "frontend");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new frontend and set with attributes from post
+ * @return array
+ */
+ public function addFrontendAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("frontend")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->frontends->frontend->Add();
+ $node->setNodes($this->request->getPost("frontend"));
+ return $this->save($mdlCP, $node, "frontend");
+ }
+ return $result;
+ }
+
+ /**
+ * delete frontend by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delFrontendAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->frontends->frontend->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * toggle frontend by uuid (enable/disable)
+ * @param $uuid item unique id
+ * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle
+ * @return array status
+ */
+ public function toggleFrontendAction($uuid, $enabled = null)
+ {
+
+ $result = array("result" => "failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('frontends.frontend.' . $uuid);
+ if ($node != null) {
+ if ($enabled == "0" || $enabled == "1") {
+ $node->enabled = (string)$enabled;
+ } elseif ((string)$node->enabled == "1") {
+ $node->enabled = "0";
+ } else {
+ $node->enabled = "1";
+ }
+ $result['result'] = $node->enabled;
+ // if item has toggled, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search haproxy frontends
+ * @return array
+ */
+ public function searchFrontendsAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->frontends->frontend);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("enabled", "name", "description","frontendid"),
+ "name"
+ );
+ }
+
+ /**
+ * retrieve backend settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getBackendAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('backends.backend.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("backend" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->backends->backend->add() ;
+ return array("backend" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update backend with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setBackendAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("backend")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('backends.backend.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("backend"));
+ return $this->save($mdlCP, $node, "backend");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new backend and set with attributes from post
+ * @return array
+ */
+ public function addBackendAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("backend")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->backends->backend->Add();
+ $node->setNodes($this->request->getPost("backend"));
+ return $this->save($mdlCP, $node, "backend");
+ }
+ return $result;
+ }
+
+ /**
+ * delete backend by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delBackendAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->backends->backend->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * toggle backend by uuid (enable/disable)
+ * @param $uuid item unique id
+ * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle
+ * @return array status
+ */
+ public function toggleBackendAction($uuid, $enabled = null)
+ {
+
+ $result = array("result" => "failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('backends.backend.' . $uuid);
+ if ($node != null) {
+ if ($enabled == "0" || $enabled == "1") {
+ $node->enabled = (string)$enabled;
+ } elseif ((string)$node->enabled == "1") {
+ $node->enabled = "0";
+ } else {
+ $node->enabled = "1";
+ }
+ $result['result'] = $node->enabled;
+ // if item has toggled, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search haproxy backends
+ * @return array
+ */
+ public function searchBackendsAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->backends->backend);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("enabled", "name", "description", "backendid"),
+ "name"
+ );
+ }
+
+ /**
+ * retrieve server settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getServerAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('servers.server.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("server" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->servers->server->add() ;
+ return array("server" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update server with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setServerAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("server")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('servers.server.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("server"));
+ return $this->save($mdlCP, $node, "server");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new server and set with attributes from post
+ * @return array
+ */
+ public function addServerAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("server")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->servers->server->Add();
+ $node->setNodes($this->request->getPost("server"));
+ return $this->save($mdlCP, $node, "server");
+ }
+ return $result;
+ }
+
+ /**
+ * delete server by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delServerAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->servers->server->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search servers
+ * @return array
+ */
+ public function searchServersAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->servers->server);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("name", "address", "port", "description", "serverid"),
+ "name"
+ );
+ }
+
+ /**
+ * retrieve healthcheck settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getHealthcheckAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('healthchecks.healthcheck.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("healthcheck" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->healthchecks->healthcheck->add() ;
+ return array("healthcheck" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update healthcheck with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setHealthcheckAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("healthcheck")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('healthchecks.healthcheck.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("healthcheck"));
+ return $this->save($mdlCP, $node, "healthcheck");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new healthcheck and set with attributes from post
+ * @return array
+ */
+ public function addHealthcheckAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("healthcheck")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->healthchecks->healthcheck->Add();
+ $node->setNodes($this->request->getPost("healthcheck"));
+ return $this->save($mdlCP, $node, "healthcheck");
+ }
+ return $result;
+ }
+
+ /**
+ * delete healthcheck by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delHealthcheckAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->healthchecks->healthcheck->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search healthchecks
+ * @return array
+ */
+ public function searchHealthchecksAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->healthchecks->healthcheck);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("name", "description", "healthcheckid"),
+ "name"
+ );
+ }
+
+ /**
+ * retrieve acl settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getAclAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('acls.acl.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("acl" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->acls->acl->add() ;
+ return array("acl" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update acl with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setAclAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("acl")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('acls.acl.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("acl"));
+ return $this->save($mdlCP, $node, "acl");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new acl and set with attributes from post
+ * @return array
+ */
+ public function addAclAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("acl")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->acls->acl->Add();
+ $node->setNodes($this->request->getPost("acl"));
+ return $this->save($mdlCP, $node, "acl");
+ }
+ return $result;
+ }
+
+ /**
+ * delete acl by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delAclAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->acls->acl->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search acls
+ * @return array
+ */
+ public function searchAclsAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->acls->acl);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("name", "description", "aclid"),
+ "name"
+ );
+ }
+
+ /**
+ * retrieve action settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getActionAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('actions.action.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("action" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->actions->action->add() ;
+ return array("action" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update action with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setActionAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("action")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('actions.action.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("action"));
+ return $this->save($mdlCP, $node, "action");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new action and set with attributes from post
+ * @return array
+ */
+ public function addActionAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("action")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->actions->action->Add();
+ $node->setNodes($this->request->getPost("action"));
+ return $this->save($mdlCP, $node, "action");
+ }
+ return $result;
+ }
+
+ /**
+ * delete action by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delActionAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->actions->action->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search actions
+ * @return array
+ */
+ public function searchActionsAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->actions->action);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("name", "description", "actionid"),
+ "name"
+ );
+ }
+
+ /**
+ * retrieve lua settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getLuaAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('luas.lua.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("lua" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->luas->lua->add() ;
+ return array("lua" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update lua with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setLuaAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("lua")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('luas.lua.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("lua"));
+ return $this->save($mdlCP, $node, "lua");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new lua and set with attributes from post
+ * @return array
+ */
+ public function addLuaAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("lua")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->luas->lua->Add();
+ $node->setNodes($this->request->getPost("lua"));
+ return $this->save($mdlCP, $node, "lua");
+ }
+ return $result;
+ }
+
+ /**
+ * delete lua by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delLuaAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->luas->lua->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * toggle lua by uuid (enable/disable)
+ * @param $uuid item unique id
+ * @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle
+ * @return array status
+ */
+ public function toggleLuaAction($uuid, $enabled = null)
+ {
+
+ $result = array("result" => "failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('luas.lua.' . $uuid);
+ if ($node != null) {
+ if ($enabled == "0" || $enabled == "1") {
+ $node->enabled = (string)$enabled;
+ } elseif ((string)$node->enabled == "1") {
+ $node->enabled = "0";
+ } else {
+ $node->enabled = "1";
+ }
+ $result['result'] = $node->enabled;
+ // if item has toggled, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search luas
+ * @return array
+ */
+ public function searchLuasAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->luas->lua);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("enabled", "name", "description", "luaid"),
+ "name"
+ );
+ }
+
+ /**
+ * retrieve errorfile settings or return defaults
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function getErrorfileAction($uuid = null)
+ {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('errorfiles.errorfile.'.$uuid);
+ if ($node != null) {
+ // return node
+ return array("errorfile" => $node->getNodes());
+ }
+ } else {
+ // generate new node, but don't save to disc
+ $node = $mdlCP->errorfiles->errorfile->add() ;
+ return array("errorfile" => $node->getNodes());
+ }
+ return array();
+ }
+
+ /**
+ * update errorfile with given properties
+ * @param $uuid item unique id
+ * @return array
+ */
+ public function setErrorfileAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("errorfile")) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ $node = $mdlCP->getNodeByReference('errorfiles.errorfile.'.$uuid);
+ if ($node != null) {
+ $node->setNodes($this->request->getPost("errorfile"));
+ return $this->save($mdlCP, $node, "errorfile");
+ }
+ }
+ }
+ return array("result"=>"failed");
+ }
+
+ /**
+ * add new errorfile and set with attributes from post
+ * @return array
+ */
+ public function addErrorfileAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost() && $this->request->hasPost("errorfile")) {
+ $mdlCP = new HAProxy();
+ $node = $mdlCP->errorfiles->errorfile->Add();
+ $node->setNodes($this->request->getPost("errorfile"));
+ return $this->save($mdlCP, $node, "errorfile");
+ }
+ return $result;
+ }
+
+ /**
+ * delete errorfile by uuid
+ * @param $uuid item unique id
+ * @return array status
+ */
+ public function delErrorfileAction($uuid)
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ $mdlCP = new HAProxy();
+ if ($uuid != null) {
+ if ($mdlCP->errorfiles->errorfile->del($uuid)) {
+ // if item is removed, serialize to config and save
+ $mdlCP->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * search errorfiles
+ * @return array
+ */
+ public function searchErrorfilesAction()
+ {
+ $this->sessionClose();
+ $mdlCP = new HAProxy();
+ $grid = new UIModelGrid($mdlCP->errorfiles->errorfile);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("name", "description", "errorfileid"),
+ "name"
+ );
+ }
+}
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php
new file mode 100644
index 0000000000..47c3ed6d1b
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/Api/StatisticsController.php
@@ -0,0 +1,77 @@
+configdRun("haproxy statistics info");
+ $response = json_decode($responseRaw, true);
+ return $response;
+ }
+
+ /**
+ * get counters
+ * @return array|mixed
+ */
+ public function countersAction($zoneid = 0)
+ {
+ $backend = new Backend();
+ $responseRaw = $backend->configdRun("haproxy statistics stat");
+ $response = json_decode($responseRaw, true);
+ return $response;
+ }
+
+ /**
+ * get tables
+ * @return array|mixed
+ */
+ public function tablesAction($zoneid = 0)
+ {
+ $backend = new Backend();
+ $responseRaw = $backend->configdRun("haproxy statistics table");
+ $response = json_decode($responseRaw, true);
+ return $response;
+ }
+}
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php
new file mode 100644
index 0000000000..8e02527783
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/IndexController.php
@@ -0,0 +1,59 @@
+view->title = "HAProxy Load Balancer";
+ // include form definitions
+ $this->view->mainForm = $this->getForm("main");
+ $this->view->formDialogFrontend = $this->getForm("dialogFrontend");
+ $this->view->formDialogBackend = $this->getForm("dialogBackend");
+ $this->view->formDialogServer = $this->getForm("dialogServer");
+ $this->view->formDialogHealthcheck = $this->getForm("dialogHealthcheck");
+ $this->view->formDialogAction = $this->getForm("dialogAction");
+ $this->view->formDialogAcl = $this->getForm("dialogAcl");
+ $this->view->formDialogLua = $this->getForm("dialogLua");
+ $this->view->formDialogErrorfile = $this->getForm("dialogErrorfile");
+ // pick the template to serve
+ $this->view->pick('OPNsense/HAProxy/index');
+ }
+}
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/StatisticsController.php b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/StatisticsController.php
new file mode 100644
index 0000000000..b0814052fd
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/StatisticsController.php
@@ -0,0 +1,44 @@
+view->title = "HAProxy Load Balancer / Statistics";
+ // choose template
+ $this->view->pick('OPNsense/HAProxy/statistics');
+ }
+}
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml
new file mode 100644
index 0000000000..34579e5725
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml
@@ -0,0 +1,52 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml
new file mode 100644
index 0000000000..96e7f306b9
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml
@@ -0,0 +1,81 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml
new file mode 100644
index 0000000000..d812696fb0
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogBackend.xml
@@ -0,0 +1,157 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogErrorfile.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogErrorfile.xml
new file mode 100644
index 0000000000..c8b2b352d6
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogErrorfile.xml
@@ -0,0 +1,26 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml
new file mode 100644
index 0000000000..9f04d755cf
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogFrontend.xml
@@ -0,0 +1,170 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml
new file mode 100644
index 0000000000..27b397f94e
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogHealthcheck.xml
@@ -0,0 +1,106 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml
new file mode 100644
index 0000000000..72a791e424
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogLua.xml
@@ -0,0 +1,26 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml
new file mode 100644
index 0000000000..1a399cb2b2
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogServer.xml
@@ -0,0 +1,54 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml
new file mode 100644
index 0000000000..94c57028c4
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/main.xml
@@ -0,0 +1,190 @@
+
diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/ACL/ACL.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/ACL/ACL.xml
new file mode 100644
index 0000000000..f03a9221d8
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/ACL/ACL.xml
@@ -0,0 +1,11 @@
+
+
+
+ WebCfg - Services: HAProxy page
+ Allow access to the 'Services: HAProxy' page.
+
+ ui/haproxy/*
+ api/haproxy/*
+
+
+
diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php
new file mode 100644
index 0000000000..f5526b04ab
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.php
@@ -0,0 +1,83 @@
+frontends->frontend->__items as $frontend) {
+ if ((string)$frontendid === (string)$frontend->frontendid) {
+ return $frontend;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * retrieve backend by number
+ * @param $backendid frontend number
+ * @return null|BaseField backend details
+ */
+ public function getByBackendID($backendid)
+ {
+ foreach ($this->backends->backend->__items as $backend) {
+ if ((string)$backendid === (string)$backend->backendid) {
+ return $backend;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * check if module is enabled
+ * @return bool is the HAProxy enabled (1 or more active frontends)
+ */
+ public function isEnabled()
+ {
+ foreach ($this->frontends->frontend->__items as $frontend) {
+ if ((string)$frontend->enabled == "1") {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml
new file mode 100644
index 0000000000..74ae5cbbef
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml
@@ -0,0 +1,901 @@
+
+ //OPNsense/HAProxy
+
+ the HAProxy load balancer
+
+
+
+
+ 0
+ Y
+
+
+
+ 1
+ 500000
+ Please specify a value between 1 and 500000.
+ N
+
+
+ 1
+ 1
+ 128
+ Please specify a value between 1 and 128.
+ Y
+
+
+ 1024
+ 1024
+ 16384
+ Please specify a value between 1024 and 16384.
+ Y
+
+
+ 16384
+ 1024
+ 1048576
+ Please specify a value between 1024 and 1048576.
+ N
+
+
+ 16384
+ 1024
+ 1048576
+ Please specify a value between 1024 and 1048576.
+ N
+
+
+ 0
+ 0
+ 50
+ Please specify a value between 0 and 50.
+ Y
+
+
+ 0
+ 0
+ 1024
+ Please specify a value between 0 and 1024.
+ N
+
+
+ N
+
+
+
+
+ 1
+ 500000
+ Please specify a value between 1 and 500000.
+ N
+
+
+ 1
+ 1000000
+ 30000
+ Please specify a value between 1 and 1000000.
+ N
+
+
+ 100
+ 1000000
+ 30000
+ Please specify a value between 100 and 1000000.
+ N
+
+
+ 100
+ 1000000
+ 30000
+ Please specify a value between 100 and 1000000.
+ N
+
+
+ 1
+ 100
+ 3
+ Please specify a value between 1 and 100.
+ Y
+
+
+ N
+ x-1
+
+ redispatch on every 3rd retry
+ redispatch on every 2nd retry
+ redispatch on every retry
+ Disable redispatching
+ redispatch on the last retry [default]
+ redispatch on the 2nd retry prior to the last retry
+ redispatch on the 3rd retry prior to the last retry
+
+
+
+
+
+ 127.0.0.1
+ /^((([0-9a-zA-Z._\-\*]+:[0-9]+)([,]){0,1}))*/u
+ lower
+ Please provide a valid host, i.e. 127.0.0.1 or 10.0.0.1:514.
+ Y
+
+
+ Y
+ local0
+
+ alert
+ audit
+ auth2
+ auth
+ cron2
+ cron
+ daemon
+ ftp
+ kern
+ local0
+ local1
+ local2
+ local3
+ local4
+ local5
+ local6
+ local7
+ lpr
+ mail
+ news
+ ntp
+ syslog
+ user
+ uucp
+
+
+
+ N
+
+ alert
+ crit
+ debug
+ emerg
+ err
+ info
+ notice
+ warning
+
+
+
+ 64
+ 65535
+ Please specify a value between 64 and 65535.
+ N
+
+
+
+
+ 0
+ N
+
+
+ 1024
+ 65535
+ 8822
+ Please specify a value between 1024 and 65535.
+ Y
+
+
+ 0
+ N
+
+
+
+ N
+ Y
+ /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u
+ lower
+ Please provide a valid listen address, i.e. 10.0.0.1:8080 or haproxy.example.com:8999.
+
+
+ 0
+ N
+
+
+ N
+ Y
+ /^((([0-9a-zA-Z._\-]+:[0-9a-zA-Z._\-]+)([,]){0,1}))*/u
+ Please provide a valid user and password, i.e. user:secret123.
+
+
+
+
+
+
+ N
+
+
+ 1
+ Y
+
+
+ Y
+ /^([0-9a-zA-Z._]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+
+
+ N
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+
+
+ Y
+ Y
+
+ /^((([0-9a-zA-Z._\-\*]+:[0-9]+)([,]){0,1}))*/u
+ lower
+ Please provide a valid listen address, i.e. 127.0.0.1:8080 or www.example.com:443.
+
+
+ Y
+ http
+
+ HTTP / HTTPS (SSL offloading) [default]
+ SSL / HTTPS (TCP mode)
+ TCP
+
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ backends.backend
+ name
+
+
+ Related item not found
+ N
+
+
+
+ 0
+ Y
+
+
+ N
+ Y
+ Please select a valid certificate from the list.
+
+
+ N
+
+
+
+ 1
+ 500000
+ Please specify a value between 1 and 500000.
+ N
+
+
+ 1
+ 1000000
+ Please specify a value between 1 and 1000000.
+ N
+
+
+
+ 0
+ Y
+
+
+ 0
+ Y
+
+
+ 0
+ Y
+
+
+ 0
+ Y
+
+
+ 0
+ Y
+
+
+ 0
+ Y
+
+
+ Y
+ http-keep-alive
+
+ http-keep-alive [default]
+ http-tunnel
+ httpclose
+ http-server-close
+ forceclose
+
+
+
+ N
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ actions.action
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ errorfiles.errorfile
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+
+
+
+
+ N
+
+
+ 1
+ Y
+
+
+ Y
+ /^([0-9a-zA-Z._]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+
+
+ N
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+
+
+ Y
+ http
+
+ HTTP (Layer 7) [default]
+ TCP (Layer 4)
+
+
+
+ Y
+ source
+
+ Source-IP Hash [default]
+ Round Robin
+ Static Round Robin
+ Least Connections
+ URI Hash (only HTTP mode)
+
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ servers.server
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+ 1
+ Y
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ healthchecks.healthcheck
+ name
+
+
+ Related item not found
+ N
+ N
+
+
+ 0
+ N
+
+
+
+ N
+ sourceipv4
+
+ Stick on Source-IP [default]
+ Stick on Source-IPv6
+ Stick on existing Cookie value
+ Stick on RDP-Cookie
+
+
+
+ Y
+ 30m
+ /^([0-9]{1,5}[d|h|m|s|ms]{1})*/u
+ lower
+ Should be a number between 1 and 5 characters followed by either "d", "h", "m", "s" or "ms".
+
+
+ Y
+ 50k
+ /^([0-9]{1,5}[k|m|g]{1})*/u
+ lower
+ Should be a number between 1 and 5 characters followed by either "k", "m" or "g".
+
+
+ N
+ /^([0-9a-zA-Z\.,_\-:]){1,1024}$/u
+ Does not look like a valid cookie name (most special characters are not allowed).
+
+
+ 1
+ 10000
+ Please specify a value between 1 and 10000.
+ N
+
+
+
+ 100
+ 1000000
+ Please specify a value between 100 and 1000000.
+ N
+
+
+ 100
+ 1000000
+ Please specify a value between 100 and 1000000.
+ N
+
+
+ 1
+ 100
+ Please specify a value between 1 and 100.
+ N
+
+
+ N
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ actions.action
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ errorfiles.errorfile
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+
+
+
+
+ /^([0-9a-zA-Z._]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ Y
+
+
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ /^([0-9a-zA-Z\.,_\-:]){0,1024}$/u
+ Please specify a valid servername or IP address.
+ Y
+
+
+ 80
+ 1
+ 65535
+ Please specify a value between 1 and 65535.
+ Y
+
+
+ Y
+ active
+
+ active [default]
+ backup
+ disabled
+ inactive
+
+
+
+ 0
+ Y
+
+
+ 0
+ 256
+ Please specify a value between 0 and 256.
+ N
+
+
+ 1000
+ 500
+ 100000
+ Please specify a value between 500 and 100000.
+ Y
+
+
+
+
+
+
+ /^([0-9a-zA-Z._]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ Y
+
+
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ Y
+ http
+
+ TCP
+ HTTP [default]
+ Agent
+ LDAP
+ MySQL
+ PostgreSQL
+ Redis
+ SMTP
+ ESMTP
+ SSL
+
+
+
+ 250
+ 1000000
+ 1000
+ Please specify a value between 250 and 1000000.
+ N
+
+
+
+ N
+ options
+
+ OPTIONS [default]
+ HEAD
+ GET
+ PUT
+ POST
+ DELETE
+ TRACE
+
+
+
+ /
+ /^(.*){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ N
+ http10
+
+ HTTP/1.0 [default]
+ HTTP/1.1
+
+
+
+ localhost
+ /^([0-9a-zA-Z._\-]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ 0
+ N
+
+
+ N
+
+ test the exact string match for the HTTP status code
+ test a regular expression for the HTTP status code
+ test the exact string match in the HTTP response body
+ test a regular expression on the HTTP response body
+
+
+
+ 0
+ N
+
+
+ N
+
+
+ 1
+ 65535
+ Please specify a value between 1 and 65535.
+ N
+
+
+ /^([0-9a-zA-Z._\-]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ /^([0-9a-zA-Z._\-]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+
+
+
+
+ N
+
+
+ /^([0-9a-zA-Z._]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ Y
+
+
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ Y
+
+ Host starts with
+ Host ends with
+ Host matches
+ Host regex
+ Host contains
+ Path starts with
+ Path ends with
+ Path matches
+ Path regex
+ Path contains
+ URL parameter contains
+ SSL Client certificate verify error result
+ SSL Client certificate is valid
+ SSL Client issued by CA common-name
+ Source IP matches IP or Alias
+ Minimum count usable servers
+ Traffic is http (no value needed)
+ Traffic is ssl (no value needed)
+ SNI TLS extension matches
+ SNI TLS extension contains
+ SNI TLS extension starts with
+ SNI TLS extension ends with
+ SNI TLS extension regex
+ Custom ACL
+
+
+
+ 0
+ Y
+
+
+ N
+
+
+ N
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ backends.backend
+ name
+
+
+ Related item not found
+ N
+
+
+
+
+
+
+ /^([0-9a-zA-Z._]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ Y
+
+
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ Y
+ if
+
+ IF [default]
+ UNLESS
+
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ acls.acl
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+ N
+ and
+
+ AND [default]
+ OR
+
+
+
+ Y
+
+ Use Backend
+ Use Server
+ http-request allow
+ http-request deny
+ http-request tarpit
+ http-request auth
+ http-request redirect
+ http-request lua action
+ http-request lua service
+ http-request header add
+ http-request header set
+ http-request header delete
+ http-request header replace
+ http-request header replace value
+ http-response allow
+ http-response deny
+ http-response lua script
+ http-response header add
+ http-response header set
+ http-response header delete
+ http-response header replace
+ http-response header replace value
+ tcp-request connection accept
+ tcp-request connection reject
+ tcp-request content accept
+ tcp-request content reject
+ tcp-request content lua script
+ tcp-request content use-service
+ tcp-response content accept
+ tcp-response content close
+ tcp-response content reject
+ tcp-response content lua script
+ Custom
+
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ backends.backend
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+
+
+ OPNsense.HAProxy.HAProxy
+ servers.server
+ name
+
+
+ Related item not found
+ Y
+ N
+
+
+ N
+
+
+ N
+
+
+ N
+
+
+
+
+
+
+ Y
+
+
+ 1
+ Y
+
+
+ /^([0-9a-zA-Z_\-]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ Y
+
+
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ Y
+
+
+
+
+
+
+ Y
+
+
+ /^([0-9a-zA-Z_\-]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ Y
+
+
+ /^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u
+ Should be a string between 1 and 255 characters.
+ N
+
+
+ Y
+ 503
+
+ 200
+ 400
+ 403
+ 405
+ 408
+ 429
+ 500
+ 502
+ 503
+ 504
+
+
+
+ Y
+
+
+
+
+
diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml
new file mode 100644
index 0000000000..613c9c2163
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Menu/Menu.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt
new file mode 100644
index 0000000000..891095a5ef
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/index.volt
@@ -0,0 +1,657 @@
+{#
+
+Copyright (C) 2016 Frank Wall
+OPNsense® is Copyright © 2014 – 2015 by Deciso B.V.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+#}
+
+
+
+
+{% for tab in mainForm['tabs']|default([]) %}
+ {% if tab['subtabs']|default(false) %}
+ {# Tab with dropdown #}
+
+ {# Find active subtab #}
+ {% set active_subtab="" %}
+ {% for subtab in tab['subtabs']|default({}) %}
+ {% if subtab[0]==mainForm['activetab']|default("") %}
+ {% set active_subtab=subtab[0] %}
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+ {{tab[1]}}
+
+
+ {% else %}
+ {# Standard Tab #}
+
+
+ {{tab[1]}}
+
+
+ {% endif %}
+{% endfor %}
+ {# add custom content #}
+ {{ lang._('Frontends') }}
+ {{ lang._('Backends') }}
+ {{ lang._('Servers') }}
+ {{ lang._('Health Checks') }}
+ {{ lang._('Actions') }}
+ {{ lang._('ACLs') }}
+ {{ lang._('Lua Scripts') }}
+ {{ lang._('Error Files') }}
+
+
+
+ {% for tab in mainForm['tabs']|default([]) %}
+ {% if tab['subtabs']|default(false) %}
+ {# Tab with dropdown #}
+ {% for subtab in tab['subtabs']|default({})%}
+
+ {{ partial("layout_partials/base_form",['fields':subtab[2],'id':'frm_'~subtab[0],'data_title':subtab[1],'apply_btn_id':'save_'~subtab[0]])}}
+
+ {% endfor %}
+ {% endif %}
+ {% if tab['subtabs']|default(false)==false %}
+
+ {{ partial("layout_partials/base_form",['fields':tab[2],'id':'frm_'~tab[0],'apply_btn_id':'save_'~tab[0]])}}
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+
+ {{ lang._('Enabled') }}
+ {{ lang._('Frontend ID') }}
+ {{ lang._('Frontend Name') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Enabled') }}
+ {{ lang._('Backend ID') }}
+ {{ lang._('Backend Name') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Server id') }}
+ {{ lang._('Server Name') }}
+ {{ lang._('Server Address') }}
+ {{ lang._('Server Port') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Health Check ID') }}
+ {{ lang._('Health Check Name') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Action ID') }}
+ {{ lang._('Action Name') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('ACL id') }}
+ {{ lang._('ACL Name') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Enabled') }}
+ {{ lang._('Lua ID') }}
+ {{ lang._('Lua Script Name') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Error File ID') }}
+ {{ lang._('Name') }}
+ {{ lang._('Description') }}
+ {{ lang._('Commands') }}
+ {{ lang._('ID') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Apply') }}
+ {{ lang._('Test syntax') }}
+
+
+
+
+
+
+{# include dialogs #}
+{{ partial("layout_partials/base_dialog",['fields':formDialogFrontend,'id':'DialogFrontend','label':'Edit Frontend'])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogBackend,'id':'DialogBackend','label':'Edit Backend'])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogServer,'id':'DialogServer','label':'Edit Server'])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogHealthcheck,'id':'DialogHealthcheck','label':'Edit Health Check'])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogAction,'id':'DialogAction','label':'Edit Action'])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogAcl,'id':'DialogAcl','label':'Edit ACL'])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogLua,'id':'DialogLua','label':'Edit Lua Script'])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogErrorfile,'id':'DialogErrorfile','label':'Edit Error File'])}}
diff --git a/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/statistics.volt b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/statistics.volt
new file mode 100644
index 0000000000..667747d71c
--- /dev/null
+++ b/net/haproxy/src/opnsense/mvc/app/views/OPNsense/HAProxy/statistics.volt
@@ -0,0 +1,311 @@
+{#
+
+Copyright (C) 2016 Frank Wall
+OPNsense® is Copyright © 2014 – 2016 by Deciso B.V.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+#}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Refresh') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('id') }}
+ {{ lang._('Proxy') }}
+ {{ lang._('Server') }}
+ {{ lang._('Status') }}
+ {{ lang._('Last Change') }}
+ {{ lang._('Weight') }}
+ {{ lang._('Active') }}
+ {{ lang._('Downtime') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Refresh') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('id') }}
+ {{ lang._('Backend/Frontend') }}
+ {{ lang._('Server') }}
+ {{ lang._('Queue') }}
+ {{ lang._('Max') }}
+ {{ lang._('Limit') }}
+ {{ lang._('Session Rate') }}
+ {{ lang._('Max') }}
+ {{ lang._('Limit') }}
+ {{ lang._('Sessions') }}
+ {{ lang._('Max') }}
+ {{ lang._('Limit') }}
+ {{ lang._('Total') }}
+ {{ lang._('Bytes In') }}
+ {{ lang._('Out') }}
+ {{ lang._('Denied Req') }}
+ {{ lang._('Resp') }}
+ {{ lang._('Errors Req') }}
+ {{ lang._('Conn') }}
+ {{ lang._('Resp') }}
+ {{ lang._('Warnings Retr') }}
+ {{ lang._('Redis') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Refresh') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Table') }}
+ {{ lang._('Type') }}
+ {{ lang._('Size') }}
+ {{ lang._('Used') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lang._('Refresh') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ partial("layout_partials/base_dialog_processing") }}
diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportCerts.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportCerts.php
new file mode 100755
index 0000000000..e9aca1c45e
--- /dev/null
+++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportCerts.php
@@ -0,0 +1,66 @@
+#!/usr/local/bin/php
+object();
+if (isset($configObj->OPNsense->HAProxy->frontends)) {
+ foreach ($configObj->OPNsense->HAProxy->frontends->children() as $frontend) {
+ if (!isset($frontend->ssl)) {
+ continue;
+ }
+ // multiple comma-separated values are possible
+ $certs = explode(',', $frontend->ssl->certificates);
+ foreach ($certs as $cert_refid) {
+ // if the frontend has a cert attached, search for its contents
+ if ($cert_refid != "") {
+ foreach ($configObj->cert as $cert) {
+ if ($cert_refid == (string)$cert->refid) {
+ // generate cert pem file
+ $pem_content = str_replace("\n\n", "\n", str_replace("\r", "", base64_decode((string)$cert->crt)));
+ $pem_content .= str_replace("\n\n", "\n", str_replace("\r", "", base64_decode((string)$cert->prv)));
+ $output_pem_filename = "/var/etc/haproxy/ssl/" . $cert_refid . ".pem" ;
+ file_put_contents($output_pem_filename, $pem_content);
+ chmod($output_pem_filename, 0600);
+ echo "certificate exported to " . $output_pem_filename . "\n";
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php
new file mode 100755
index 0000000000..0241624515
--- /dev/null
+++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php
@@ -0,0 +1,54 @@
+#!/usr/local/bin/php
+object();
+if (isset($configObj->OPNsense->HAProxy->errorfiles)) {
+ foreach ($configObj->OPNsense->HAProxy->errorfiles->children() as $errorfile) {
+ $ef_name = (string)$errorfile->name;
+ $ef_id = (string)$errorfile->id;
+ if ($ef_id != "") {
+ $ef_content = str_replace("\n\n", "\n", str_replace("\r", "", (string)$errorfile->content));
+ $ef_filename = "/var/etc/haproxy/errorfiles/" . $ef_id . ".txt" ;
+ file_put_contents($ef_filename, $ef_content);
+ chmod($ef_filename, 0600);
+ echo "error file exported to " . $ef_filename . "\n";
+ }
+ }
+}
diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php
new file mode 100755
index 0000000000..599bb52125
--- /dev/null
+++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php
@@ -0,0 +1,57 @@
+#!/usr/local/bin/php
+object();
+if (isset($configObj->OPNsense->HAProxy->luas)) {
+ foreach ($configObj->OPNsense->HAProxy->luas->children() as $lua) {
+ if (!isset($lua->enabled)) {
+ continue;
+ }
+ $lua_name = (string)$lua->name;
+ $lua_id = (string)$lua->id;
+ if ($lua_id != "") {
+ $lua_content = str_replace("\n\n", "\n", str_replace("\r", "", (string)$lua->content));
+ $lua_filename = "/var/etc/haproxy/lua/" . $lua_id . ".lua" ;
+ file_put_contents($lua_filename, $lua_content);
+ chmod($lua_filename, 0600);
+ echo "lua script exported to " . $lua_filename . "\n";
+ }
+ }
+}
diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/queryStats.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/queryStats.php
new file mode 100755
index 0000000000..08c780d512
--- /dev/null
+++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/queryStats.php
@@ -0,0 +1,116 @@
+#!/usr/local/bin/php
+ 1) $a = array_combine($stat_csv[0], $a);
+ });
+ array_shift($stat_csv); # remove column header
+ foreach ($stat_csv as &$value) {
+ // Add unique identifier.
+ if ( ! empty($value['pxname']) and $value['pxname'] != null ) {
+ $value['id'] = $value['pxname'] . '/' . $value['svname'];
+ }
+ }
+ return $stat_csv;
+}
+
+switch ($argv[1]) {
+ case 'info':
+ $result = showInfo();
+ echo json_encode($result);
+ break;
+ case 'table':
+ $result = showTable();
+ echo json_encode($result);
+ break;
+ case 'stat':
+ $result = showStat();
+ echo json_encode($result);
+ break;
+ default:
+ echo "not a valid argument\n";
+}
diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/setup.sh b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/setup.sh
new file mode 100755
index 0000000000..b8c57f0f0f
--- /dev/null
+++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/setup.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+HAPROXY_DIRS="/var/log/haproxy /var/run/haproxy /var/etc/haproxy/ssl /var/etc/haproxy/lua /var/etc/haproxy/errorfiles"
+
+for directory in ${HAPROXY_DIRS}; do
+ mkdir -p ${directory}
+ chown -R www:www ${directory}
+ chmod -R 750 ${directory}
+done
+
+# chroot dir must not be writable
+chmod 550 /var/run/haproxy
+
+# export required data to filesystem
+/usr/local/opnsense/scripts/OPNsense/HAProxy/exportCerts.php > /dev/null 2>&1
+/usr/local/opnsense/scripts/OPNsense/HAProxy/exportLuaScripts.php > /dev/null 2>&1
+/usr/local/opnsense/scripts/OPNsense/HAProxy/exportErrorFiles.php > /dev/null 2>&1
+
+exit 0
diff --git a/net/haproxy/src/opnsense/service/conf/actions.d/actions_haproxy.conf b/net/haproxy/src/opnsense/service/conf/actions.d/actions_haproxy.conf
new file mode 100644
index 0000000000..a40d035d7e
--- /dev/null
+++ b/net/haproxy/src/opnsense/service/conf/actions.d/actions_haproxy.conf
@@ -0,0 +1,47 @@
+[setup]
+command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh
+parameters:
+type:script_output
+message:setup haproxy service requirements
+
+[start]
+command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh; /usr/local/etc/rc.d/haproxy start
+parameters:
+type:script
+message:starting haproxy
+
+[stop]
+command:/usr/local/etc/rc.d/haproxy stop; /usr/bin/killall haproxy; exit 0
+parameters:
+type:script
+message:stopping haproxy
+
+[restart]
+command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh; /usr/local/etc/rc.d/haproxy restart
+parameters:
+type:script
+message:restarting haproxy
+
+[reconfigure]
+command:/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh; /usr/local/etc/rc.d/haproxy reload
+parameters:
+type:script
+message:reconfiguring haproxy
+
+[configtest]
+command:/usr/local/etc/rc.d/haproxy configtest 2>&1 || exit 0
+parameters:
+type:script_output
+message:testing haproxy configuration
+
+[status]
+command:/usr/local/etc/rc.d/haproxy status || exit 0
+parameters:
+type:script_output
+message:requesting haproxy status
+
+[statistics]
+command:/usr/local/opnsense/scripts/OPNsense/HAProxy/queryStats.php
+parameters:%s
+type:script_output
+message:requesting haproxy statistics
diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+MANIFEST b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+MANIFEST
new file mode 100644
index 0000000000..5e864b8e60
--- /dev/null
+++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+MANIFEST
@@ -0,0 +1,8 @@
+name: opnsense-haproxy
+version: 1.0
+origin: opnsense/haproxy
+comment: load balancer
+desc: Reliable, high performance TCP/HTTP load balancer
+maintainer: opnsense at moov.de
+www: https://haproxy.org
+prefix: /
diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+TARGETS b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+TARGETS
new file mode 100644
index 0000000000..6e4c913d20
--- /dev/null
+++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/+TARGETS
@@ -0,0 +1,2 @@
+haproxy.conf:/usr/local/etc/haproxy.conf
+rc.conf.d:/etc/rc.conf.d/haproxy
diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf
new file mode 100644
index 0000000000..8716b2c0bb
--- /dev/null
+++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf
@@ -0,0 +1,790 @@
+#
+# Automatically generated configuration.
+# Do not edit this file manually.
+
+{# ############################### #}
+{# MACROS #}
+{# ############################### #}
+
+{# Macro expects a CSV list of Error Files and validates them. #}
+{% macro ErrorFiles(linkedData) -%}
+{% if linkedData is defined %}
+{# # remember all Errorfiles to avoid duplicate HTTP codes #}
+{% set http_codes_seen = [] %}
+{% for errorfile in linkedData.split(",") %}
+{% set errorfile_data = helpers.getUUID(errorfile) %}
+{% if errorfile_data.code in http_codes_seen %}
+ # ERROR FILE INVALID: {{errorfile_data.name}}
+ # ERROR: this HTTP code is already used by another error file in this context
+{% else %}
+{% do http_codes_seen.append(errorfile_data.code) %}
+ # ERROR FILE: {{errorfile_data.name}}
+ errorfile {{errorfile_data.code|replace("x", "")}} /var/etc/haproxy/errorfiles/{{errorfile_data.id}}.txt
+{% endif %}
+{% endfor %}
+{% else %}
+# ERROR: ErrorFiles called with empty data
+{% endif %}
+{%- endmacro %}
+
+{# Macro expects a CSV list of Actions and validates them. #}
+{% macro AclsAndActions(linkedData) -%}
+{% if linkedData is defined %}
+{# # remember all ACLs to avoid duplicate declarations #}
+{% set acls_seen = [] %}
+{% for action in linkedData.split(",") %}
+{% set action_data = helpers.getUUID(action) %}
+{# # collect ACLs for this action #}
+{% set action_acls = [] %}
+{# # collect ACL errors (may disable Action) #}
+{% set acl_errors = '0' %}
+{# # An action with no ACLs is invalid #}
+{% if action_data.linkedAcls|default("") != "" %}
+{% for acl in action_data.linkedAcls.split(",") %}
+{% set acl_data = helpers.getUUID(acl) %}
+{% set acl_enabled = '1' %}
+{% do action_acls.append('acl_' ~ acl_data.id) %}
+{# # check if this ACL was already defined in this scope #}
+{% if acl_data.id in acls_seen %}
+{# # DEBUG: ignoring duplicate ACL {{acl_data.name}} #}
+{% continue %}
+{% endif %}
+{% do acls_seen.append(acl_data.id) %}
+{% set acl_options = [] %}
+{% if acl_data.expression == 'host_starts_with' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('hdr_beg(host) -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'host_ends_with' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('hdr_end(host) -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'host_matches' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('hdr(host) -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'host_regex' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('hdr_reg(host) -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'host_contains' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('hdr_dir(host) -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'path_starts_with' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('path_beg -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'path_ends_with' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('path_end -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'path_matches' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('path -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'path_regex' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('path_reg -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'path_contains' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('path_dir -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'url_parameter' %}
+{% if acl_data.value|default("") != "" and acl_data.urlparam|default("") != "" %}
+{% do acl_options.append('url_param(' ~ acl_data.urlparam ~ ') -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'ssl_c_verify_code' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('ssl_c_verify ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'ssl_c_verify' %}
+{% do acl_options.append('ssl_c_verify 0') %}
+{% elif acl_data.expression == 'ssl_c_ca_commonname' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('ssl_c_i_dn(CN) ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'source_ip' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('src ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'backendservercount' %}
+{% do acl_options.append('') %}
+{% if acl_data.value|default("") != "" and acl_data.queryBackend|default("") != "" %}
+{% do acl_options.append('nbsrv(backend_' ~ acl_data.queryBackend ~ ') ge ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'traffic_is_http' %}
+{% do acl_options.append('req.proto_http') %}
+{% elif acl_data.expression == 'traffic_is_ssl' %}
+{% do acl_options.append('req.ssl_ver gt 0') %}
+{% elif acl_data.expression == 'ssl_sni_matches' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('req.ssl_sni -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'ssl_sni_contains' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('req.ssl_sni -m sub -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'ssl_sni_starts_with' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('req.ssl_sni -m beg -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'ssl_sni_ends_with' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('req.ssl_sni -m end -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'ssl_sni_regex' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append('req.ssl_sni -m reg -i ' ~ acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif acl_data.expression == 'custom_acl' %}
+{% if acl_data.value|default("") != "" %}
+{% do acl_options.append(acl_data.value) %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% else %}
+{% set acl_enabled = '0' %}
+ # ERROR: unsupported ACL expression
+{% endif %}
+{# # check if ACL is valid #}
+{% if acl_enabled == '1' %}
+ # ACL: {{acl_data.name}}
+ acl acl_{{acl_data.id}} {{acl_options|join(' ')}}
+{% else %}
+{% set acl_errors = acl_errors + 1 %}
+ # ACL INVALID: {{acl_data.name}}
+{% endif %}
+{% endfor %}
+{# # NOTE: We're ignoring actions if any ACL is erroneous, #}
+{# # because doing otherwise would lead to unpredictable behaviour. #}
+{% if acl_errors == '0' %}
+{% set action_enabled = '1' %}
+{% set action_options = [] %}
+{% if action_data.type == 'use_backend' %}
+{% if action_data.useBackend|default("") != "" %}
+{% set acl_backend_data = helpers.getUUID(action_data.useBackend) %}
+{% do action_options.append('use_backend ' ~ acl_backend_data.name) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'use_server' %}
+{% if action_data.useServer|default("") != "" %}
+{% set server_data = helpers.getUUID(action_data.useServer) %}
+{% do action_options.append('use-server ' ~ server_data.name) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_allow' %}
+{% do action_options.append('http-request allow') %}
+{% elif action_data.type == 'http-request_deny' %}
+{% do action_options.append('http-request deny') %}
+{% elif action_data.type == 'http-request_tarpit' %}
+{% do action_options.append('http-request tarpit') %}
+{% elif action_data.type == 'http-request_auth' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('http-request auth ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_redirect' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('http-request redirect ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_lua' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('http-request lua.' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_use-service' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('http-request use-service lua.' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_add-header' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
+{% do action_options.append('http-request add-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_set-header' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
+{% do action_options.append('http-request set-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_del-header' %}
+{% if action_data.actionName|default("") != "" %}
+{% do action_options.append('http-request del-header' ~ action_data.actionName) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_replace-header' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
+{% do action_options.append('http-request replace-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-request_replace-value' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
+{% do action_options.append('http-request replace-value ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-response_allow' %}
+{% do action_options.append('http-response allow') %}
+{% elif action_data.type == 'http-response_deny' %}
+{% do action_options.append('http-response deny') %}
+{% elif action_data.type == 'http-response_lua' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('http-response lua.' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-response_add-header' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
+{% do action_options.append('http-response add-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-response_set-header' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" %}
+{% do action_options.append('http-response set-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-response_del-header' %}
+{% if action_data.actionName|default("") != "" %}
+{% do action_options.append('http-response del-header' ~ action_data.actionName) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-response_replace-header' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
+{% do action_options.append('http-response replace-header ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'http-response_replace-value' %}
+{% if action_data.actionValue|default("") != "" and action_data.actionName|default("") != "" and action_data.actionFind|default("") != "" %}
+{% do action_options.append('http-response replace-value ' ~ action_data.actionName ~ ' ' ~ action_data.actionFind ~ ' ' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'tcp-request_connection_accept' %}
+{% do action_options.append('tcp-request connection accept') %}
+{% elif action_data.type == 'tcp-request_connection_reject' %}
+{% do action_options.append('tcp-request connection reject') %}
+{% elif action_data.type == 'tcp-request_content_accept' %}
+{% do action_options.append('tcp-request content accept') %}
+{% elif action_data.type == 'tcp-request_content_reject' %}
+{% do action_options.append('tcp-request content reject') %}
+{% elif action_data.type == 'tcp-request_content_lua' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('tcp-request content lua.' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'tcp-request_content_use-service' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('tcp-request content use-service lua.' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'tcp-response_content_accept' %}
+{% do action_options.append('tcp-response content accept') %}
+{% elif action_data.type == 'tcp-response_content_close' %}
+{% do action_options.append('tcp-response content close') %}
+{% elif action_data.type == 'tcp-response_content_reject' %}
+{% do action_options.append('tcp-response content reject') %}
+{% elif action_data.type == 'tcp-response_content_lua' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append('tcp-response content lua.' ~ action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% elif action_data.type == 'custom' %}
+{% if action_data.actionValue|default("") != "" %}
+{% do action_options.append(action_data.actionValue) %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: missing parameters
+{% endif %}
+{% else %}
+{% set action_enabled = '0' %}
+ # ERROR: unsupported action type
+{% endif %}
+{# # check if action is valid #}
+{% if action_enabled == '1' %}
+{% if action_data.operator == 'or' %}
+{% set join_operator = ' || ' %}
+{% else %}
+{% set join_operator = ' ' %}
+{% endif %}
+ # ACTION: {{action_data.name}}
+ {{action_options|join(' ')}} {{action_data.testType}} {{action_acls|join(join_operator)}}
+{% else %}
+ # ACTION INVALID: {{action_data.name}}
+{% endif %}
+{% else %}
+ # ACTION INVALID: {{action_data.name}}
+ # ACL ERROR COUNT: {{acl_errors}}
+{% endif %}
+{% else %}
+ # ERROR: got action with empty linkedAcls
+{% endif %}
+{% endfor %}
+{% else %}
+# ERROR: AclsAndActions called with empty data
+{% endif %}
+{%- endmacro %}
+
+{% if not (helpers.exists('OPNsense.HAProxy.general') and OPNsense.HAProxy.general.enabled|default("0") == "1") %}
+#
+# NOTE: HAProxy is currently DISABLED
+#
+{% endif %}
+
+{# ############################### #}
+{# GLOBAL #}
+{# ############################### #}
+
+global
+ uid 80
+ gid 80
+ chroot /var/run/haproxy
+ daemon
+ stats socket /var/run/haproxy.socket level admin
+ nbproc {{OPNsense.HAProxy.general.tuning.nbproc}}
+{% if helpers.exists('OPNsense.HAProxy.general.tuning.maxConnections') %}
+ maxconn {{OPNsense.HAProxy.general.tuning.maxConnections}}
+{% endif %}
+{% if helpers.exists('OPNsense.HAProxy.general.tuning.maxDHSize') %}
+ tune.ssl.default-dh-param {{OPNsense.HAProxy.general.tuning.maxDHSize}}
+{% endif %}
+{% if OPNsense.HAProxy.general.tuning.spreadChecks|default("") != "" %}
+ spread-checks {{OPNsense.HAProxy.general.tuning.spreadChecks}}
+{% endif %}
+{% if OPNsense.HAProxy.general.tuning.checkBufferSize|default("") != "" %}
+ tune.chksize {{OPNsense.HAProxy.general.tuning.checkBufferSize}}
+{% endif %}
+{% if OPNsense.HAProxy.general.tuning.bufferSize|default("") != "" %}
+ tune.bufsize {{OPNsense.HAProxy.general.tuning.bufferSize}}
+{% endif %}
+{% if OPNsense.HAProxy.general.tuning.luaMaxMem|default("") != "" %}
+ tune.lua.maxmem {{OPNsense.HAProxy.general.tuning.luaMaxMem}}
+{% endif %}
+{# # logging configuration #}
+{% set logging = [] %}
+{% do logging.append(OPNsense.HAProxy.general.logging.host) %}
+{% do logging.append('len ' ~ OPNsense.HAProxy.general.logging.length) if OPNsense.HAProxy.general.logging.length|default("") != "" %}
+{% do logging.append(OPNsense.HAProxy.general.logging.facility) %}
+{% do logging.append(OPNsense.HAProxy.general.logging.level) if OPNsense.HAProxy.general.logging.level|default("") != "" %}
+ log {{logging|join(' ')}}
+{% if OPNsense.HAProxy.luas.lua is defined %}
+ # lua scripts
+{% for lua in helpers.toList('OPNsense.HAProxy.luas.lua') %}
+{% if lua.enabled == '1' %}
+ # lua script: {{lua.name}}
+ lua-load /var/etc/haproxy/lua/{{lua.id}}.lua
+{% endif %}
+{% endfor %}
+{% endif %}
+
+
+{# ############################### #}
+{# DEFAULTS #}
+{# ############################### #}
+
+defaults
+ log global
+{% if OPNsense.HAProxy.general.defaults.redispatch|default("") != "" %}
+ option redispatch {{OPNsense.HAProxy.general.defaults.redispatch|replace("x", "")}}
+{% endif %}
+{% if OPNsense.HAProxy.general.defaults.maxConnections|default("") != "" %}
+ maxconn {{OPNsense.HAProxy.general.defaults.maxConnections}}
+{% endif %}
+{% if OPNsense.HAProxy.general.defaults.timeoutClient|default("") != "" %}
+ timeout client {{OPNsense.HAProxy.general.defaults.timeoutClient}}
+{% endif %}
+{% if OPNsense.HAProxy.general.defaults.timeoutConnect|default("") != "" %}
+ timeout connect {{OPNsense.HAProxy.general.defaults.timeoutConnect}}
+{% endif %}
+{% if OPNsense.HAProxy.general.defaults.timeoutServer|default("") != "" %}
+ timeout server {{OPNsense.HAProxy.general.defaults.timeoutServer}}
+{% endif %}
+{% if OPNsense.HAProxy.general.defaults.retries|default("") != "" %}
+ retries {{OPNsense.HAProxy.general.defaults.retries}}
+{% endif %}
+
+{# ############################### #}
+{# FRONTENDS #}
+{# ############################### #}
+
+{% if helpers.exists('OPNsense.HAProxy.frontends') %}
+{% for frontend in helpers.toList('OPNsense.HAProxy.frontends.frontend') %}
+{% if frontend.enabled=='1' %}
+# Frontend: {{frontend.name}} ({{frontend.description}})
+frontend {{frontend.name}}
+{# # collect ssl certs (if configured) #}
+{% if frontend.ssl_certificates|default("") != "" %}
+{% set ssl_certs = [] %}
+{% for cert in frontend.ssl_certificates.split(",") %}
+{% do ssl_certs.append('crt /var/etc/haproxy/ssl/' ~ cert ~ '.pem') %}
+{% endfor %}
+{% endif %}
+{# # advanced ssl options #}
+{% if frontend.ssl_customOptions|default("") != "" %}
+{# # add a space to separate it from other ssl params #}
+{% set ssl_options = frontend.ssl_customOptions ~ ' ' %}
+{% endif %}
+{# # bind/listen configuration #}
+{% if frontend.bind|default("") != "" %}
+{% for bind in frontend.bind.split(",") %}
+ bind {{bind}} name {{bind}} {% if ssl_certs|default("") != "" %}ssl {{ ssl_options }}{{ssl_certs|join(' ')}}{% endif %}
+
+{% endfor %}
+{% endif %}
+ mode {{frontend.mode}}
+ option {{frontend.connectionBehaviour}}
+{# # select backend #}
+{% if frontend.defaultBackend|default("") != "" %}
+{% set backend_data = helpers.getUUID(frontend.defaultBackend) %}
+ default_backend {{backend_data.name}}
+{% endif %}
+ # tuning options
+{% if frontend.tuning_maxConnections is defined %}
+ maxconn {{frontend.tuning_maxConnections}}
+{% endif %}
+{% if frontend.tuning_timeoutClient is defined %}
+ timeout client {{frontend.tuning_timeoutClient}}
+{% elif OPNsense.HAProxy.general.defaults.timeoutClient is defined %}
+ timeout client {{OPNsense.HAProxy.general.defaults.timeoutClient}}
+{% endif %}
+ # logging options
+{% if frontend.logging_dontLogNull=='1' %}
+ option dontlognull
+{% endif %}
+{% if frontend.logging_dontLogNormal=='1' %}
+ option dontlog-normal
+{% endif %}
+{% if frontend.logging_logSeparateErrors=='1' %}
+ option log-separate-errors
+{% endif %}
+{% if frontend.logging_detailedLog=='1' %}
+{# # automatically select the best-suited log type #}
+{% if frontend.mode == 'tcp' %}
+ option tcplog
+{% else %}
+ option httplog
+{% endif %}
+{% endif %}
+{% if frontend.logging_socketStats=='1' %}
+ option socket-stats
+{% endif %}
+{# # action and ACL configuration #}
+{% if frontend.linkedActions|default("") != "" -%}
+{# # call macro to evaluate ACLs and actions #}
+{{ AclsAndActions(frontend.linkedActions) }}
+{%- endif %}
+{# # error files #}
+{% if frontend.linkedErrorfiles|default("") != "" %}
+{# # call macro to evaluate error files #}
+{{ ErrorFiles(frontend.linkedErrorfiles) }}
+{% endif %}
+{% if frontend.customOptions|default("") != "" %}
+ # WARNING: pass through options below this line
+{% for customOpt in frontend.customOptions.split("\n") %}
+ {{customOpt}}
+{% endfor %}
+{% endif %}
+
+{% else %}
+# Frontend (DISABLED): {{frontend.description}}
+
+{% endif %}
+{% endfor %}
+{% endif %}
+
+{# ############################### #}
+{# BACKENDS #}
+{# ############################### #}
+
+{% if helpers.exists('OPNsense.HAProxy.backends') %}
+{% for backend in helpers.toList('OPNsense.HAProxy.backends.backend') %}
+{# # ignore disabled backends and those without a server #}
+{% if backend.enabled == '1' and backend.linkedServers|default("") != "" %}
+# Backend: {{backend.name}} ({{backend.description}})
+backend {{backend.name}}
+{# # store additional parameters for the "server" entries #}
+{% set healthcheck_additions = [] %}
+{% if backend.healthCheck|default("") != "" and backend.healthCheckEnabled == '1' %}
+{% set healthcheck_enabled = '1' %}
+{% if backend.healthCheckLogStatus == '1' %}
+ option log-health-checks
+{% endif %}
+{% set healthcheck_data = helpers.getUUID(backend.healthCheck) %}
+ # health check: {{healthcheck_data.name}}
+{# # health check option #}
+{% set healthcheck_options = [] %}
+{% if healthcheck_data.type|default("") == "" %}
+{% set healthcheck_enabled = '1' %}
+{% elif healthcheck_data.type == 'tcp' %}
+{# # TCP check does not require additional options #}
+{% elif healthcheck_data.type == 'http' %}
+{% do healthcheck_options.append('httpchk') %}
+{# # HTTP method must be uppercase #}
+{% do healthcheck_options.append(healthcheck_data.http_method|upper) %}
+{% do healthcheck_options.append(healthcheck_data.http_uri) %}
+{% do healthcheck_options.append('HTTP/1.0') if healthcheck_data.http_version == 'http10' %}
+{# # HTTP Host header requires HTTP 1.1 #}
+{% do healthcheck_options.append('HTTP/1.1') if healthcheck_data.http_version == 'http11' and healthcheck_data.http_host|default("") == "" %}
+{% do healthcheck_options.append('HTTP/1.1\\r\\nHost:\ ' ~ healthcheck_data.http_host) if healthcheck_data.http_version == 'http11' and healthcheck_data.http_host|default("") != "" %}
+ option {{healthcheck_options|join(' ')}}
+{# # custom HTTP health check option #}
+{% if healthcheck_data.http_expressionEnabled|default("") == '1' %}
+{# # validate options #}
+{% if healthcheck_data.http_expression|default("") == "" or healthcheck_data.http_value|default("") == "" %}
+ # ERROR: invalid custom HTTP health check, missing "expression" or "value"
+{% else %}
+{% set healthcheck_customhttp = [] %}
+{% do healthcheck_customhttp.append(healthcheck_data.http_expression) %}
+{% do healthcheck_customhttp.append('!') if healthcheck_data.http_negate == '1' %}
+{# # XXX: some values must be properly escaped (whitespace) #}
+{% do healthcheck_customhttp.append(healthcheck_data.http_value) %}
+ http-check expect {{healthcheck_customhttp|join(' ')}}
+{% endif %}
+{% endif %}
+{% elif healthcheck_data.type == 'agent' %}
+{% if healthcheck_data.agentPort|default("") != "" %}
+{% do healthcheck_additions.append('agent-check agent-port ' ~ healthcheck_data.agentPort) %}
+{% else %}
+ # ERROR: agent-check configured, but agent-port was not specified
+{% endif %}
+{% elif healthcheck_data.type == 'ldap' %}
+ option ldap-check
+{% elif healthcheck_data.type == 'mysql' or healthcheck_data.type == 'pgsql' %}
+{% if healthcheck_data.dbUser|default("") != "" %}
+ option {{healthcheck_data.type}}-check user {{healthcheck_data.dbUser}}
+{% else %}
+ # ERROR: {{healthcheck_data.type}} check configured, but db user was not specified
+{% endif %}
+{% elif healthcheck_data.type == 'redis' %}
+ option redis-check
+{% elif healthcheck_data.type == 'smtp' %}
+ option smtpchk HELO {{healthcheck_data.smtpDomain}}
+{% elif healthcheck_data.type == 'esmtp' %}
+ option smtpchk EHLO {{healthcheck_data.smtpDomain}}
+{% elif healthcheck_data.type == 'ssl' %}
+ option ssl-hello-chk
+{% endif %}
+{% else %}
+ # health checking is DISABLED
+{% set healthcheck_enabled = '0' %}
+{% endif %}
+{% for server in backend.linkedServers.split(",") %}
+{% set server_data = helpers.getUUID(server) %}
+{# # collect optional server parameters #}
+{% set server_options = [] %}
+{# # check if health check is enabled #}
+{% if healthcheck_enabled == '1' %}
+{% do server_options.append('check') %}
+{% do server_options.append('inter ' ~ server_data.checkInterval) %}
+{# # add all additions from healthchecks here #}
+{% do server_options.append(healthcheck_additions|join(' ')) if healthcheck_additions.length != '0' %}
+{% endif %}
+{# # server weight #}
+{% do server_options.append('weight ' ~ server_data.weight) if server_data.weight|default("") != "" %}
+{# # server role/mode #}
+{% do server_options.append(server_data.mode) if server_data.mode|default("") != "active" %}
+ server {{server_data.name}} {{server_data.address}}:{{server_data.port}} {{server_options|join(' ')}}
+{% endfor %}
+{# # XXX: Usually the frontend and the backend are in the same mode, #}
+{# # but we have no way to know what frontend uses this backend. #}
+{# # Hence we can't automatically set the mode and thus need a #}
+{# # (redundant) GUI option for this. #}
+ mode {{backend.mode}}
+ balance {{backend.algorithm}}
+{# # ignore if stickiness is disabled (set to "None") #}
+{% if backend.stickiness_pattern|default("") != "" %}
+ # stickiness
+{% if backend.stickiness_pattern == "sourceipv4" %}
+ stick-table type ip size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
+ stick on src
+{% elif backend.stickiness_pattern == "sourceipv6" %}
+ stick-table type ipv6 size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
+ stick on src
+{% elif backend.stickiness_pattern == "cookievalue" %}
+ stick-table type string len {{backend.stickiness_cookielength}} size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
+ stick store-response res.cook({{backend.stickiness_cookiename}})
+ stick on req.cook({{backend.stickiness_cookiename}})
+{% elif backend.stickiness_pattern == "rdpcookie" %}
+ stick-table type binary len {{backend.stickiness_cookielength}} size {{backend.stickiness_size}} expire {{backend.stickiness_expire}}
+ stick on req.rdp_cookie(mstshash)
+{% endif %}
+{% endif %}
+ # tuning options
+{% if backend.tuning_timeoutConnect|default("") != "" %}
+ timeout connect {{backend.tuning_timeoutConnect}}
+{% elif OPNsense.HAProxy.general.defaults.timeoutConnect is defined %}
+ timeout connect {{OPNsense.HAProxy.general.defaults.timeoutConnect}}
+{% endif %}
+{% if backend.tuning_timeoutServer|default("") != "" %}
+ timeout server {{backend.tuning_timeoutServer}}
+{% elif OPNsense.HAProxy.general.defaults.timeoutServer is defined %}
+ timeout server {{OPNsense.HAProxy.general.defaults.timeoutServer}}
+{% endif %}
+{% if backend.tuning_retries|default("") != "" %}
+ retries {{backend.tuning_retries}}
+{% endif %}
+{# # action and ACL configuration #}
+{% if backend.linkedActions|default("") != "" -%}
+{# # call macro to evaluate ACLs and actions #}
+{{ AclsAndActions(backend.linkedActions) }}
+{%- endif %}
+{# # error files #}
+{% if backend.linkedErrorfiles|default("") != "" %}
+{# # call macro to evaluate error files #}
+{{ ErrorFiles(backend.linkedErrorfiles) }}
+{% endif %}
+{% if backend.customOptions|default("") != "" %}
+ # WARNING: pass through options below this line
+{% for customOpt in backend.customOptions.split("\n") %}
+ {{customOpt}}
+{% endfor %}
+{% endif %}
+
+{% else %}
+# Backend (DISABLED): {{backend.description}}
+
+{% endif %}
+{% endfor %}
+{% endif %}
+
+{# ############################### #}
+{# STATISTICS #}
+{# ############################### #}
+
+{% if OPNsense.HAProxy.general.stats.enabled|default("") == "1" %}
+{# # enable local stats #}
+listen local_statistics
+ bind 127.0.0.1:{{OPNsense.HAProxy.general.stats.port}}
+ mode http
+ stats uri /haproxy?stats
+ stats realm HAProxy\ statistics
+ stats admin if TRUE
+
+{# # remote stats are optional #}
+{% if OPNsense.HAProxy.general.stats.remoteEnabled|default("") == "1" %}
+{% if OPNsense.HAProxy.general.stats.remoteBind|default("") != "" %}
+listen remote_statistics
+{% for bind in OPNsense.HAProxy.general.stats.remoteBind.split(",") %}
+ bind {{bind}}
+{% endfor %}
+ mode http
+ stats uri /haproxy?stats
+ stats realm HAProxy\ statistics
+ stats hide-version
+{# # enable authentication? #}
+{% if OPNsense.HAProxy.general.stats.authEnabled|default("") == "1" %}
+{% if OPNsense.HAProxy.general.stats.users|default("") != "" %}
+{% for statsuser in OPNsense.HAProxy.general.stats.users.split(",") %}
+ stats auth {{statsuser}}
+{% endfor %}
+{% endif %}
+{% endif %}
+{% else %}
+# ERROR: remote statistics disabled, because no listen address was specified
+{% endif %}
+{% endif %}
+{% else %}
+# statistics are DISABLED
+{% endif %}
+
+
diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/rc.conf.d b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/rc.conf.d
new file mode 100644
index 0000000000..48bb5a3786
--- /dev/null
+++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/rc.conf.d
@@ -0,0 +1,4 @@
+haproxy_enable="{% if helpers.exists('OPNsense.HAProxy.general.enabled') and OPNsense.HAProxy.general.enabled|default("0") == "1" %}YES{% else %}NO{% endif %}"
+haproxy_pidfile="/var/run/haproxy.pid"
+haproxy_config="/usr/local/etc/haproxy.conf"
+# haproxy_flags=""