Mobile wallpaper
1820 字
9 分钟

OPNSense添加WebDAV备份插件

2025-02-26
浏览量 加载中...
本文主要内容

由于OPNsense 24.1默认配置备份只支持本地和Goolge Drive两种备份方式,本文主要是给OPNSense添加WebDAV备份功能。

配置流程#

添加WebDAV备份插件#

  1. 进入OPNSense,打开安全shell设置,ssh连接进入系统shell。 CleanShot 2025-02-26 at 12.53.16@2x.png
  2. 创建文件夹 mkdir -p /usr/local/opnsense/mvc/app/models/OPNsense/Backup/ 后,添加核心实现逻辑代码。
    /usr/local/opnsense/mvc/app/library/OPNsense/Backup/WebDAV.php
    cat << 'EOF' > /usr/local/opnsense/mvc/app/library/OPNsense/Backup/WebDAV.php
    <?php
    /*
    * Copyright (C) 2018 Deciso B.V.
    * Copyright (C) 2018 Fabian Franz
    * 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.
    */
    namespace OPNsense\Backup;
    use OPNsense\Core\Config;
    /**
    * Class WebDAV backup
    * @package OPNsense\Backup
    */
    class WebDAV extends Base implements IBackupProvider
    {
    /**
    * get required (user interface) fields for backup connector
    * @return array configuration fields, types and description
    */
    public function getConfigurationFields()
    {
    $fields = array(
    array(
    "name" => "enabled",
    "type" => "checkbox",
    "label" => gettext("Enable"),
    "value" => null
    ),
    array(
    "name" => "url",
    "type" => "text",
    "label" => gettext("URL"),
    "help" => gettext("The Base URL to WebDAV without trailing slash. For example: https://dav.example.com"),
    "value" => null
    ),
    array(
    "name" => "user",
    "type" => "text",
    "label" => gettext("User Name"),
    "help" => gettext("The name you use for logging into your WebDAV account"),
    "value" => null
    ),
    array(
    "name" => "password",
    "type" => "password",
    "label" => gettext("Password"),
    "help" => gettext("The password you use for logging into your WebDAV account"),
    "value" => null
    ),
    array(
    "name" => "password_encryption",
    "type" => "password",
    "label" => gettext("Encryption Password (Optional)"),
    "help" => gettext("A password to encrypt your configuration"),
    "value" => null
    ),
    array(
    "name" => "backupdir",
    "type" => "text",
    "label" => gettext("Directory Name without leading slash, starting from user's root"),
    "value" => 'OPNsense-Backup'
    )
    );
    $webdav = new WebDAVSettings();
    foreach ($fields as &$field) {
    $field['value'] = (string)$webdav->getNodeByReference($field['name']);
    }
    return $fields;
    }
    /**
    * backup provider name
    * @return string user friendly name
    */
    public function getName()
    {
    return gettext("WebDAV");
    }
    /**
    * validate and set configuration
    * @param array $conf configuration array
    * @return array of validation errors when not saved
    * @throws \OPNsense\Base\ModelException
    * @throws \ReflectionException
    */
    public function setConfiguration($conf)
    {
    $webdav = new WebDAVSettings();
    $this->setModelProperties($webdav, $conf);
    $validation_messages = $this->validateModel($webdav);
    if (empty($validation_messages)) {
    $webdav->serializeToConfig();
    Config::getInstance()->save();
    }
    return $validation_messages;
    }
    /**
    * perform backup
    * @return array filelist
    * @throws \OPNsense\Base\ModelException
    * @throws \ReflectionException
    */
    public function backup()
    {
    $cnf = Config::getInstance();
    $webdav = new WebDAVSettings();
    if ($cnf->isValid() && !empty((string)$webdav->enabled)) {
    $config = $cnf->object();
    $url = (string)$webdav->url;
    $username = (string)$webdav->user;
    $password = (string)$webdav->password;
    $backupdir = (string)$webdav->backupdir;
    $crypto_password = (string)$webdav->password_encryption;
    $hostname = $config->system->hostname . '.' . $config->system->domain;
    $configname = 'config-' . $hostname . '-' . date('Y-m-d_H_i_s') . '.xml';
    // backup source data to local strings (plain/encrypted)
    $confdata = file_get_contents('/conf/config.xml');
    if (!empty($crypto_password)) {
    $confdata = $this->encrypt($confdata, $crypto_password);
    }
    // Check if destination directory exists, create (full path) if not
    try {
    $this->create_directory($url, $username, $password, $backupdir);
    } catch (\Exception $e) {
    return array();
    }
    try {
    $this->upload_file_content(
    $url,
    $username,
    $password,
    $backupdir,
    $configname,
    $confdata
    );
    // do not list directories
    return array_filter(
    $this->listFiles($url, $username, $password, "/$backupdir/", false),
    function ($filename) {
    return (substr($filename, -1) !== '/');
    }
    );
    } catch (\Exception $e) {
    return array();
    }
    }
    }
    /**
    * dir listing
    * @param string $url remote location
    * @param string $username username
    * @param string $password password to use
    * @param string $directory location to list
    * @param bool $only_dirs only list directories
    * @return array
    * @throws \Exception
    */
    public function listFiles($url, $username, $password, $directory = '/', $only_dirs = true)
    {
    $result = $this->curl_request(
    "$url$directory",
    $username,
    $password,
    'PROPFIND',
    "Error while fetching filelist from WebDAV '{$directory}' path"
    );
    //remove line breaks from xml string
    $xml = preg_replace("/\r?\n/", '', $result['response']);
    // workaround - simplexml seems to be broken when using namespaces - remove them.
    //$xml = str_replace(['<D:', '</D:', '<d:', '</d:', '<lp1:', '</lp1:'], ['<', '</', '<', '</', '<', '</'], $xml);
    // better workaround: remove any namespace
    $xml = preg_replace('/\s+xmlns:[^=]+="[^"]*"/', '', $xml);
    $xml = preg_replace('/\b(\w+)\:/i', '', $xml);
    $xml = simplexml_load_string($xml);
    $ret = array();
    //parse URL for a check if path exists
    $parsedUrl = parse_url($url);
    foreach ($xml->children() as $response) {
    // d:response
    if ($response->getName() == 'response') {
    $fileurl = (string)$response->href;
    //check if URL has a path
    if (isset($parsedUrl['path'])) {
    //URL DOES have a path - extracting path from fileurl
    $dirname = explode($parsedUrl['path'], $fileurl, 2)[1];
    }
    else {
    //URL does NOT have a path
    $dirname = $fileurl;
    }
    if (
    $response->propstat->prop->resourcetype->children()->count() > 0 &&
    $response->propstat->prop->resourcetype->children()[0]->getName() == 'collection' &&
    $only_dirs
    ) {
    $ret[] = $dirname;
    } elseif (!$only_dirs) {
    $ret[] = $dirname;
    }
    }
    }
    return $ret;
    }
    /**
    * upload file
    * @param string $url remote location
    * @param string $username remote user
    * @param string $password password to use
    * @param string $backupdir remote directory
    * @param string $filename filename to use
    * @param string $local_file_content contents to save
    * @throws \Exception when upload fails
    */
    public function upload_file_content($url, $username, $password, $backupdir, $filename, $local_file_content)
    {
    $this->curl_request(
    $url . "/$backupdir/$filename",
    $username,
    $password,
    'PUT',
    'cannot execute PUT',
    $local_file_content
    );
    }
    /**
    * create new remote directory if doesn't exist
    * @param string $url remote location
    * @param string $username remote user
    * @param string $password password to use
    * @param string $backupdir remote directory
    * @throws \Exception when create dir fails
    */
    public function create_directory($url, $username, $password, $backupdir)
    {
    $parent_path = dirname($backupdir);
    try {
    $directories = $this->listFiles($url, $username, $password, "/{$parent_path}");
    } catch (\Exception $e) {
    if ($backupdir == ".") {
    // We cannot create root, if we reached here there's some other problem
    syslog(LOG_ERR, "Check WebDAV configuration parameters");
    return false;
    }
    // If error assume dir doesn't exist. Create parent folder
    if ($this->create_directory($url, $username, $password, $parent_path) === false) {
    throw new \Exception();
    }
    }
    // if path exists ok
    if (in_array("/{$backupdir}/", $directories)) {
    return;
    }
    // create backupdir, because path does not exist
    $this->curl_request(
    $url . "/{$backupdir}",
    $username,
    $password,
    'MKCOL',
    'cannot execute MKCOL'
    );
    }
    /**
    * @param string $url remote location
    * @param string $username remote user
    * @param string $password password to use
    * @param string $method http method, PUT, GET, ...
    * @param string $error_message message to log on failure
    * @param null|string $postdata http body
    * @param array $headers HTTP headers
    * @return array response status
    * @throws \Exception when request fails
    */
    public function curl_request(
    $url,
    $username,
    $password,
    $method,
    $error_message,
    $postdata = null,
    $headers = array('User-Agent: OPNsense Firewall')
    ) {
    //workaround for Hetzner Storagebox
    if ($method == "PROPFIND") {
    array_push($headers, 'Depth: 1');
    }
    $curl = curl_init();
    curl_setopt_array($curl, array(
    CURLOPT_URL => $url,
    CURLOPT_CUSTOMREQUEST => $method, // Create a file in WebDAV is PUT
    CURLOPT_RETURNTRANSFER => true, // Do not output the data to STDOUT
    CURLOPT_VERBOSE => 0, // same here
    CURLOPT_MAXREDIRS => 0, // no redirects
    CURLOPT_TIMEOUT => 60, // maximum time: 1 min
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_USERPWD => $username . ":" . $password,
    CURLOPT_HTTPHEADER => $headers
    ));
    if ($postdata != null) {
    curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata);
    }
    $response = curl_exec($curl);
    $err = curl_error($curl);
    $info = curl_getinfo($curl);
    if (!($info['http_code'] == 200 || $info['http_code'] == 207 || $info['http_code'] == 201) || $err) {
    syslog(LOG_ERR, $error_message);
    syslog(LOG_ERR, json_encode($info));
    throw new \Exception();
    }
    curl_close($curl);
    return array('response' => $response, 'info' => $info);
    }
    /**
    * Is this provider enabled
    * @return boolean enabled status
    * @throws \OPNsense\Base\ModelException
    * @throws \ReflectionException
    */
    public function isEnabled()
    {
    $webdav = new WebDAVSettings();
    return (string)$webdav->enabled === "1";
    }
    }
    'EOF'
    /usr/local/opnsense/mvc/app/models/OPNsense/Backup/WebDAVSettings.php
    cat << 'EOF' > /usr/local/opnsense/mvc/app/models/OPNsense/Backup/WebDAVSettings.php
    <?php
    /**
    * Copyright (C) 2018 Fabian Franz
    *
    * 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.
    *
    */
    namespace OPNsense\Backup;
    use OPNsense\Base\BaseModel;
    /**
    * Class WebDAV
    * @package Backup
    */
    class WebDAVSettings extends BaseModel
    {
    }
    'EOF'
  3. 添加WebDAVSettings.xml,这一步让在OPNSense备份中显示WebDAV配置界面。
    Terminal window
    cat << 'EOF' > /usr/local/opnsense/mvc/app/models/OPNsense/Backup/WebDAVSettings.xml
    <model>
    <mount>//system/backup/webdav</mount>
    <version>1.0.0</version>
    <description>OPNsense WebDAV Backup Settings</description>
    <items>
    <enabled type="BooleanField">
    <default>0</default>
    <Required>Y</Required>
    </enabled>
    <url type="TextField">
    <Required>N</Required>
    <mask>/^https?:\/\/.*[^\/]$/</mask>
    <ValidationMessage>The URL must be valid without a trailing slash. For example: https://dav.example.com</ValidationMessage>
    <Constraints>
    <check001>
    <ValidationMessage>A URL for the WebDAV server must be set.</ValidationMessage>
    <type>DependConstraint</type>
    <addFields>
    <field1>enabled</field1>
    </addFields>
    </check001>
    </Constraints>
    </url>
    <user type="TextField">
    <Constraints>
    <check001>
    <ValidationMessage>A user for the WebDAV server must be set.</ValidationMessage>
    <type>DependConstraint</type>
    <addFields>
    <field1>enabled</field1>
    </addFields>
    </check001>
    </Constraints>
    </user>
    <password type="TextField">
    <Constraints>
    <check001>
    <ValidationMessage>A password for the WebDAV server must be set.</ValidationMessage>
    <type>DependConstraint</type>
    <addFields>
    <field1>enabled</field1>
    </addFields>
    </check001>
    </Constraints>
    </password>
    <password_encryption type="TextField">
    <Required>N</Required>
    </password_encryption>
    <backupdir type="TextField">
    <Required>Y</Required>
    <mask>/^([\w%+\-]+\/)*[\w+%\-]+$/</mask>
    <default>OPNsense-Backup</default>
    <ValidationMessage>The Backup Directory can only consist of alphanumeric characters, dash, underscores and slash. No leading or trailing slash.</ValidationMessage>
    </backupdir>
    </items>
    </model>
    'EOF'
  4. 进入OPNSense系统 -> 配置 -> 备份 -> 滑到最后。按需配置即可。 CleanShot 2025-02-26 at 12.59.24@2x.png
最后更新于 2025-02-26,距今已过 263 天

部分内容可能已过时

评论区

目录