diff options
author | nachoparker <nacho@ownyourbits.com> | 2019-04-30 05:06:58 +0300 |
---|---|---|
committer | nachoparker <nacho@ownyourbits.com> | 2019-05-01 03:02:58 +0300 |
commit | f34354c336614bc74df41381dafda6cea90642cc (patch) | |
tree | 8688f54a710657e25d4a820098c32913f0c54568 | |
parent | 01cd4215a530aeff3a62e3a38f07d4cc734cb1bb (diff) |
ncp-web: add backups panelv1.12.0
-rw-r--r-- | bin/ncp/BACKUPS/nc-backup.sh | 3 | ||||
-rw-r--r-- | changelog.md | 10 | ||||
-rw-r--r-- | ncp-web/backups.php | 159 | ||||
-rw-r--r-- | ncp-web/css/ncp.css | 56 | ||||
-rw-r--r-- | ncp-web/download.php | 75 | ||||
-rw-r--r-- | ncp-web/elements.php | 2 | ||||
-rw-r--r-- | ncp-web/img/defaults-white.svg | 54 | ||||
-rw-r--r-- | ncp-web/img/delete.svg | 1 | ||||
-rw-r--r-- | ncp-web/img/download.svg | 1 | ||||
-rw-r--r-- | ncp-web/index.php | 29 | ||||
-rw-r--r-- | ncp-web/js/ncp.js | 218 | ||||
-rw-r--r-- | ncp-web/ncp-launcher.php | 43 | ||||
-rw-r--r-- | ncp-web/upload.php | 47 | ||||
-rw-r--r-- | ncp.sh | 32 | ||||
-rwxr-xr-x | update.sh | 41 |
15 files changed, 746 insertions, 25 deletions
diff --git a/bin/ncp/BACKUPS/nc-backup.sh b/bin/ncp/BACKUPS/nc-backup.sh index 03c49566..b4cb457b 100644 --- a/bin/ncp/BACKUPS/nc-backup.sh +++ b/bin/ncp/BACKUPS/nc-backup.sh @@ -100,7 +100,8 @@ tar $compress_arg -cf "$destfile" \ exit 1 } rm "$dbbackup" -chmod 600 "$destfile" +chmod 640 "$destfile" +chown :www-data "$destfile" echo "backup $destfile generated" EOF diff --git a/changelog.md b/changelog.md index 5eb76739..c73e18ca 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,13 @@ -[v1.11.4](https://github.com/nextcloud/nextcloudpi/commit/62a7f45) (2019-04-28) letsencrypt: switch to apt version +[v1.12.0](https://github.com/nextcloud/nextcloudpi/commit/703ff6f) (2019-04-29) ncp-web: add backups panel -[v1.11.3 ](https://github.com/nextcloud/nextcloudpi/commit/71d8f52) (2019-04-09) nc-restore: check btrfs command +[v1.11.5](https://github.com/nextcloud/nextcloudpi/commit/01cd421) (2019-04-29) letsencrypt: force renewal by default -[v1.11.2, master](https://github.com/nextcloud/nextcloudpi/commit/3754609) (2019-04-06) armbian: fix uu +[v1.11.4 ](https://github.com/nextcloud/nextcloudpi/commit/b3c7d13) (2019-04-28) letsencrypt: switch to apt version + +[v1.11.3 ](https://github.com/nextcloud/nextcloudpi/commit/02efd61) (2019-04-09) nc-restore: check btrfs command + +[v1.11.2 ](https://github.com/nextcloud/nextcloudpi/commit/3754609) (2019-04-06) armbian: fix uu [v1.11.1 ](https://github.com/nextcloud/nextcloudpi/commit/a712935) (2019-04-05) nc-backup: fix space calculation diff --git a/ncp-web/backups.php b/ncp-web/backups.php new file mode 100644 index 00000000..2bdb08e3 --- /dev/null +++ b/ncp-web/backups.php @@ -0,0 +1,159 @@ +<!-- + NextCloudPi Web Backups Panel + + Copyleft 2019 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com> + GPL licensed (see end of file) * Use at your own risk! + + More at https://nextcloudpi.com +--> +<?php + +$bkp_cfg = file_get_contents('/usr/local/etc/ncp-config.d/nc-backup.cfg') or exit('backup config not found'); +$bkp_auto_cfg = file_get_contents('/usr/local/etc/ncp-config.d/nc-backup-auto.cfg') or exit('backup config not found'); + +$bkp_json = json_decode($bkp_cfg , true) or exit('invalid format'); +$bkp_auto_json = json_decode($bkp_auto_cfg, true) or exit('invalid format'); + +$bkp_dir = $bkp_json['params'][0]['value']; +$bkp_auto_dir = $bkp_auto_json['params'][1]['value']; + +$bkps = array(); +$bkps_auto = array(); + +if (file_exists($bkp_dir)) +{ + $bkps = array_diff(scandir($bkp_dir), array('.', '..')); + $bkps = preg_filter('/^/', $bkp_dir. '/', $bkps); +} + +if (file_exists($bkp_auto_dir)) +{ + $bkps_auto = array_diff(scandir($bkp_auto_dir), array('.', '..')); + $bkps_auto = preg_filter('/^/', $bkp_auto_dir . '/', $bkps_auto); +} + +$bkps = array_unique(array_merge($bkps, $bkps_auto)); + +if (!empty($bkps)) +{ +echo <<<HTML + <div id="backups-table"> + <table class="dashtable backuptable"> + <th>Date</th><th>Size</th><th>Compressed</th><th>Data</th><th></th> +HTML; + foreach ($bkps as $bkp) + { + $extension = pathinfo($bkp, PATHINFO_EXTENSION); + if ($extension === "tar" || $extension === "gz") + { + $compressed = ""; + if ($extension === "gz") + $compressed = '✓'; + + $date = date("Y M d @ H:i", filemtime($bkp)); + $size = round(filesize($bkp)/1024/1024) . " MiB"; + + $has_data = ''; + exec("sudo /home/www/ncp-backup-launcher.sh bkp " . escapeshellarg($bkp) . " \"$compressed\"", $output, $ret); + if ($ret == 0) + $has_data = '✓'; + + echo <<<HTML + <tr id="$bkp"> + <td class="long-field" title="$bkp">$date </td> + <td class="val-field">$size</td> + <td class="ok-field align-center">$compressed</td> + <td class="ok-field align-center">$has_data</td> + <td> + <img class="hidden-btn default-btn download-bkp" title="download" src="../img/download.svg"> + <img class="hidden-btn default-btn delete-bkp" title="delete" src="../img/delete.svg"> + <img class="hidden-btn default-btn restore-bkp" title="restore" src="../img/defaults.svg"> + </td> + </tr> +HTML; + echo '<input type="hidden" name="csrf-token" value="' . getCSRFToken() . '"/>'; + } + } +echo <<<HTML + </table> + </div> +HTML; +} else { + echo "<div>No backups found.</div>"; +} +?> + +</br></br> +<h2 class="text-title">Restore from file</h2> +<form action="upload.php" method="POST" enctype="multipart/form-data"> + <div class="restore-upload-btn-wrapper"> + <input type="file" name="backup" id="restore-upload" accept=".tar,.tar.gz"/> + <input id="restore-upload-btn" type="submit" value="Restore"/> + </div> +</form> +</br></br> + +<h2 class="text-title"><?php echo $l->__("Snapshots"); ?></h2> + +<?php + +include( '/var/www/nextcloud/config/config.php' ); + +$snap_dir = realpath($CONFIG['datadirectory'] . '/../ncp-snapshots'); +$snaps = array(); +if (file_exists($snap_dir)) +{ + $snaps = array_diff(scandir($snap_dir), array('.', '..')); + $snaps = preg_filter('/^/', $snap_dir . '/', $snaps); +} + +if (!empty($snaps)) +{ +echo <<<HTML + <div id="snapshots-table"> + <table class="dashtable backuptable"> +HTML; + foreach ($snaps as $snap) + { + exec("sudo /home/www/ncp-backup-launcher.sh chksnp " . escapeshellarg($snap), $out, $ret); + if ($ret == 0) + { + $snap_name = basename($snap); + echo <<<HTML + <tr id="$snap"> + <td class="text-align-left" title="$snap">$snap_name</td> + <td> + <img class="hidden-btn default-btn delete-snap" title="delete" src="../img/delete.svg"> + <img class="hidden-btn default-btn restore-snap" title="restore" src="../img/defaults.svg"> + </td> + </tr> +HTML; + } + } +echo <<<HTML + </table> + </div> +HTML; +} else { + echo "<div>No snapshots found.</div>"; +} +?> + +<!-- + License + + This script is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This script is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this script; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +--> diff --git a/ncp-web/css/ncp.css b/ncp-web/css/ncp.css index 17231bcd..9ed3140d 100644 --- a/ncp-web/css/ncp.css +++ b/ncp-web/css/ncp.css @@ -1076,7 +1076,7 @@ select { display: none; } -#loading-info-gif { +.loading-section-gif { display: flex; justify-content: center; align-items: center; @@ -1150,6 +1150,9 @@ select { .icon-search { background-image: url('../img/search.svg'); } +.icon-backups { + background-image: url('../img/defaults-white.svg'); +} .icon-config { background-image: url('../img/settings-white.svg'); } @@ -1223,6 +1226,18 @@ a#versionlink:hover { max-width: 210px; } +#confirmation-dialog { + position:fixed; + top:0; + bottom:0; + height:100%; + width:100%; + background-color:rgba(0, 0, 0, 0.5); + z-index:9000; + text-align:center; + cursor:pointer; +} + .dialog { display:block; background: white; @@ -1249,7 +1264,7 @@ a#versionlink:hover { opacity:0.75 } -#close-wizard { +.close-dialog-x { position: absolute; top: 5px; right: 5px; @@ -1319,6 +1334,23 @@ a#versionlink:hover { border-bottom: 1px solid #ebebeb; } +.backuptable td { + text-align: right; +} +.text-align-left { + text-align: left !important; + padding-left: 1em; +} +.backuptable th { + text-align: center; +} +#backups-content div { + text-align: center; +} +.align-center { + text-align: center !important; +} + #dashboard-suggestions { margin-bottom: 1em; } @@ -1353,7 +1385,23 @@ a#versionlink:hover { -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)'; opacity: 0.5; } + +.hidden-btn { + cursor: pointer; + -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)'; + opacity: 0; +} + +.backuptable tr:hover img { + -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)'; + opacity: 0.5; +} .pwd-btn:hover, .default-btn:hover { - -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; - opacity: 1; + -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)' !important; + opacity: 1 !important; +} +.restore-upload-btn-wrapper { + display: flex; + flex-direction: row; + align-items: center; } diff --git a/ncp-web/download.php b/ncp-web/download.php new file mode 100644 index 00000000..195ad818 --- /dev/null +++ b/ncp-web/download.php @@ -0,0 +1,75 @@ +<?php +/// +// NextCloudPi Web Panel backend +// +// Copyleft 2019 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com> +// GPL licensed (see end of file) * Use at your own risk! +// +// More at https://nextcloudpi.com +/// + +include ('csrf.php'); +session_start(); + +// CSRF check +$token = isset($_REQUEST['token']) ? $_REQUEST['token'] : ''; +if ( empty($token) || !validateCSRFToken($token) ) + exit('Unauthorized download'); + +if (!isset($_REQUEST["bkp"])) + die(); + +$file = $_REQUEST["bkp"]; + +if (!file_exists($file)) + die('File not found'); + +if (!is_readable($file)) + die('NCP does not have read permissions on this file'); + +$size = filesize($file); + +$extension = pathinfo($file, PATHINFO_EXTENSION); +if ($extension === "tar" ) + $mime_type = 'application/x-tar'; +else if( $extension === "gz") + $mime_type = 'application/x-gzip'; +else + die(); + +ob_start(); +ob_clean(); +header('Content-Description: File Transfer'); +header('Content-Type: ' . $mime_type); +header("Content-Transfer-Encoding: Binary"); +header("Content-disposition: attachment; filename=\"" . basename($file) . "\""); +header('Content-Length: ' . $size); +header('Expires: 0'); +header('Cache-Control: must-revalidate'); +header('Pragma: public'); + +$chunksize = 8 * (1024 * 1024); +if($size > $chunksize) +{ + $handle = fopen($file, 'rb') or die("Error opening file"); + + while (!feof($handle)) + { + $buffer = fread($handle, $chunksize); + echo $buffer; + + ob_flush(); + flush(); + } + + fclose($handle); +} +else + readfile($file); + +ob_flush(); +flush(); + +exit(); + +?> diff --git a/ncp-web/elements.php b/ncp-web/elements.php index 7e7b176d..afe4b2ba 100644 --- a/ncp-web/elements.php +++ b/ncp-web/elements.php @@ -123,7 +123,6 @@ function print_config_forms( $l /* translations l10n object */ ) $cfg_dir = '/usr/local/etc/ncp-config.d/'; $d_iterator = new RecursiveDirectoryIterator($bin_dir); $iterator = new RecursiveIteratorIterator($d_iterator); - $objects = new RegexIterator($iterator, '/^.+\.sh$/i', RecursiveRegexIterator::GET_MATCH); $ret = ""; $sections = array_diff(scandir($bin_dir), array('.', '..', 'l10n')); @@ -167,7 +166,6 @@ function print_sidebar( $l /* translations l10n object */, $ticks /* wether to c $cfg_dir = '/usr/local/etc/ncp-config.d/'; $d_iterator = new RecursiveDirectoryIterator($bin_dir); $iterator = new RecursiveIteratorIterator($d_iterator); - $objects = new RegexIterator($iterator, '/^.+\.sh$/i', RecursiveRegexIterator::GET_MATCH); $ret = ""; $sections = array_diff(scandir($bin_dir), array('.', '..', 'l10n')); diff --git a/ncp-web/img/defaults-white.svg b/ncp-web/img/defaults-white.svg new file mode 100644 index 00000000..0c01a2aa --- /dev/null +++ b/ncp-web/img/defaults-white.svg @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewbox="0 0 16 16" + id="svg4" + sodipodi:docname="defaults-white.svg" + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="640" + inkscape:window-height="480" + id="namedview6" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg4" /> + <path + d="m7.9319 2.4252c-3.4324 0-5.6787 2.9953-5.5301 5.8394h-1.8778l3.3924 3.4063 3.5454-3.3664h-1.8657c-0.20594-1.4772 1.0106-2.706 2.3366-2.6868 1.386 0.020855 2.4331 1.0688 2.4331 2.3758 0.07821 1.3851-1.4164 2.9788-3.4463 2.1985 0 1.0688 0.00261 2.2115 0 3.2717 3.641 0.72124 6.6389-2.1811 6.6389-5.431 0-3.0961-2.5374-5.6074-5.6266-5.6074z" + id="path2" + style="fill:#ffffff" /> +</svg> diff --git a/ncp-web/img/delete.svg b/ncp-web/img/delete.svg new file mode 100644 index 00000000..53f0b020 --- /dev/null +++ b/ncp-web/img/delete.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 16 16" width="16" height="16"><path d="M6.5 1L6 2H3c-.554 0-1 .446-1 1v1h12V3c0-.554-.446-1-1-1h-3l-.5-1zM3 5l.875 9c.06.55.573 1 1.125 1h6c.552 0 1.064-.45 1.125-1L13 5z"/></svg> diff --git a/ncp-web/img/download.svg b/ncp-web/img/download.svg new file mode 100644 index 00000000..dd2389b2 --- /dev/null +++ b/ncp-web/img/download.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 16 16" width="16" height="16"><path d="M6 1h4v7h5l-7 7-7-7h5z"/></svg> diff --git a/ncp-web/index.php b/ncp-web/index.php index c823f5a9..b2d21760 100644 --- a/ncp-web/index.php +++ b/ncp-web/index.php @@ -104,7 +104,7 @@ if ($ret == 0) { <br> <a href="wizard"> <button type="button" class="wizard-btn" id="go-wizard">{$l->__("run")} </button></a> <button type="button" class="first-run-close" id="skip-wizard" >{$l->__("skip")} </button> - <button type="button" class="first-run-close" id="close-wizard">{$l->__("close")}</button> + <button type="button" class="first-run-close close-dialog-x">{$l->__("close")}</button> <br><br> </div> </div> @@ -113,6 +113,20 @@ HTML; } ?> + <div id="confirmation-dialog" class="hidden"> + <div class='dialog'> + <br><br> + <h2 id="config-box-title">Are you sure?</h2> + <br> + <p>Click OK to confirm this operation</p> + <br> + <button type="button" id="confirmation-dialog-ok"> OK </button> + <button type="button" class="confirmation-dialog-close"> Cancel </button> + <button type="button" class="confirmation-dialog-close close-dialog-x">Close</button> + <br><br> + </div> + </div> + <header role="banner"><div id="header"> <div id="header-left"> <a href="https://ownyourbits.com" id="nextcloudpi" target="_blank" tabindex="1"> @@ -166,6 +180,11 @@ HTML; <div class="icon-dashboard"></div> </div> </div> + <div id="backups-btn" title="<?php echo $l->__("Backups and snapshots"); ?>"> + <div class="expand"> + <div class="icon-backups"></div> + </div> + </div> <div id="config-btn" title="<?php echo $l->__("Nextcloud Configuration"); ?>"> <div class="expand"> <div class="icon-config"></div> @@ -213,7 +232,13 @@ HTML; <h2 class="text-title"><?php echo $l->__("System Info"); ?></h2> <div id="dashboard-suggestions" class="table-wrapper"></div> <div id="dashboard-table" class="outputbox table-wrapper"></div> - <div id="loading-info-gif"> <img src="img/loading-small.gif"> </div> + <div id="loading-info-gif" class="loading-section-gif"> <img src="img/loading-small.gif"> </div> + </div> + + <div id="backups-wrapper" class="content-box <?php if($_GET['app'] != 'backups') echo 'hidden';?>"> + <h2 class="text-title"><?php echo $l->__("Backups"); ?></h2> + <div id="backups-content" class="table-wrapper"></div> + <div id="loading-backups-gif" class="loading-section-gif"> <img src="img/loading-small.gif"> </div> </div> <div id="nc-config-wrapper" class="content-box <?php if($_GET['app'] != 'config') echo 'hidden';?>"> diff --git a/ncp-web/js/ncp.js b/ncp-web/js/ncp.js index 556bc90e..bd0dc108 100644 --- a/ncp-web/js/ncp.js +++ b/ncp-web/js/ncp.js @@ -4,20 +4,23 @@ // Copyleft 2017 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com> // GPL licensed (see end of file) * Use at your own risk! // -// More at https://ownyourbits.com/2017/02/13/nextcloud-ready-raspberry-pi-image/ +// More at https://nextcloudpi.com /// var MINI = require('minified'); var $ = MINI.$, $$ = MINI.$$, EE = MINI.EE; -var selectedID = null; +var selectedID = null; var ncp_app_list = null; -var search_box = null; -var lock = false; +var search_box = null; +var lock = false; // URL based navigation +// TODO unify repeating code window.onpopstate = function(event) { selectedID = location.search.split('=')[1]; - if (selectedID == 'config') + if (selectedID == 'backups') + switch_to_section('backups'); + else if (selectedID == 'config') switch_to_section('nc-config'); else if (selectedID == 'dashboard') switch_to_section('dashboard'); @@ -27,14 +30,16 @@ window.onpopstate = function(event) { function errorMsg() { - $('#config-box').fill( "Something went wrong. Try refreshing the page" ); + $('#app-content').fill( "Something went wrong. Try refreshing the page" ); } function switch_to_section(section) { + // TODO unify repeating code $( '#config-wrapper > div' ).hide(); $( '#dashboard-wrapper' ).hide(); $( '#nc-config-wrapper' ).hide(); + $( '#backups-wrapper' ).hide(); $( '#' + section + '-wrapper' ).show(); $( '#app-navigation ul' ).set('-active'); selectedID = null; @@ -123,10 +128,169 @@ function print_dashboard() $('#loading-info-gif').hide(); $('#dashboard-table').ht( ret.table ); $('#dashboard-suggestions').ht( ret.suggestions ); - reload_sidebar(); + print_backups(); } ).error( errorMsg ); } +function del_bkp(button) +{ + var tr = button.up().up(); + var path = tr.get('.id'); + $.request('post', 'ncp-launcher.php', { action:'del-bkp', + value: path, + csrf_token: $( '#csrf-token' ).get( '.value' ) }).then( + function success( result ) + { + var ret = $.parseJSON( result ); + if ( ret.token ) + $('#csrf-token').set( { value: ret.token } ); + if ( ret.ret && ret.ret == '0' ) // means that the process was launched + tr.remove(); + else + console.log('failed removing ' + path); + } + ).error( errorMsg ) +} + +function restore_bkp(button) +{ + var tr = button.up().up(); + var path = tr.get('.id'); + click_app($('#nc-restore')); + history.pushState(null, selectedID, "?app=" + selectedID); + $('#nc-restore-BACKUPFILE').set({ value: path }); + $('#nc-restore-config-button').trigger('click'); +} + +function restore_snap(button) +{ + var tr = button.up().up(); + var path = tr.get('.id'); + click_app($('#nc-restore-snapshot')); + history.pushState(null, selectedID, "?app=" + selectedID); + $('#nc-restore-snapshot-SNAPSHOT').set({ value: path }); + $('#nc-restore-snapshot-config-button').trigger('click'); +} + +function del_snap(button) +{ + var tr = button.up().up(); + var path = tr.get('.id'); + $.request('post', 'ncp-launcher.php', { action:'del-snap', + value: path, + csrf_token: $('#csrf-token').get('.value') }).then( + function success( result ) + { + var ret = $.parseJSON( result ); + if ( ret.token ) + $('#csrf-token').set( { value: ret.token } ); + if ( ret.ret && ret.ret == '0' ) // means that the process was launched + tr.remove(); + else + console.log('failed removing ' + path); + } + ).error( errorMsg ) +} + +function restore_upload(button) +{ + var file = $$('#restore-upload').files[0]; + if (!file) return; + var upload_token = $('#csrf-token').get('.value'); + var form_data = new FormData(); + form_data.append('backup', file); + form_data.append('csrf_token', upload_token); + $.request('post', 'upload.php', form_data).then( + function success( result ) + { + var ret = $.parseJSON( result ); + if ( ret.token ) + $('#csrf-token').set( { value: ret.token } ); + if ( ret.ret && ret.ret == '0' ) // means that the process was launched + { + click_app($('#nc-restore')); + history.pushState(null, selectedID, "?app=" + selectedID); + $('#nc-restore-BACKUPFILE').set({ value: '/tmp/' + upload_token.replace('/', '') + file.name }); + $('#nc-restore-config-button').trigger('click'); + } + else + console.log('error uploading ' + file); + } + ).error( errorMsg ) +} + +clicked_dialog_button = null; +clicked_dialog_action = null; + +function dialog_action(button) +{ + if ( clicked_dialog_action && clicked_dialog_button) + clicked_dialog_action(clicked_dialog_button); +} + +// backups +function set_backup_handlers() +{ + $( '.download-bkp' ).on('click', function(e) + { + var tr = this.up().up(); + var path = tr.get('.id'); + window.location.replace('download.php?bkp=' + encodeURIComponent(path) + '&token=' + encodeURIComponent(tr.next().get('.value'))); + }); + $( '.delete-bkp' ).on('click', function(e) + { + $('#confirmation-dialog').show(); + clicked_dialog_action = del_bkp; + clicked_dialog_button = this; + }); + $( '.restore-bkp' ).on('click', function(e) + { + $('#confirmation-dialog').show(); + clicked_dialog_action = restore_bkp; + clicked_dialog_button = this; + }); + $( '#restore-upload-btn' ).on('click', function(e) + { + var file = $$('#restore-upload').files[0]; + if (!file) return; + $('#confirmation-dialog').show(); + clicked_dialog_action = restore_upload; + clicked_dialog_button = this; + }); + $( '.restore-snap' ).on('click', function(e) + { + $('#confirmation-dialog').show(); + clicked_dialog_action = restore_snap; + clicked_dialog_button = this; + }); + $( '.delete-snap' ).on('click', function(e) + { + $('#confirmation-dialog').show(); + clicked_dialog_action = del_snap; + clicked_dialog_button = this; + }); +} + +function print_backups() +{ + // request + $.request('post', 'ncp-launcher.php', { action:'backups', + csrf_token: $('#csrf-token-ui').get('.value') } + ).then( + function success( result ) + { + var ret = $.parseJSON( result ); + if (ret.token) + $('#csrf-token-ui').set({ value: ret.token }); + if (ret.ret && ret.ret == '0') { + $('#loading-backups-gif').hide(); + $('#backups-content').ht(ret.output); + set_backup_handlers(); + reload_sidebar(); + } + }).error( errorMsg ); +} + function reload_sidebar() { // request @@ -272,9 +436,16 @@ $(function() { if ( ret.ret == '0' ) { - if( ret.ref && ret.ref == 'nc-update' ) - window.location.reload( true ); - reload_sidebar(); + if (ret.ref) + { + if (ret.ref == 'nc-update') + window.location.reload( true ); + else if(ret.ref == 'nc-backup') + print_backups(); + if(ret.ref != 'nc-restore' && ret.ref != 'nc-backup') // FIXME PHP is reloaded asynchronously after nc-restore + reload_sidebar(); + } + $('.circle-retstatus').set('+icon-green-circle'); } else @@ -354,7 +525,6 @@ $(function() function dirname(path) { return path.replace(/\\/g,'/').replace(/\/[^\/]*$/, ''); } var span = this.up().select('span', true); - console.log(span); var path = dirname(this.get('.value')); // request @@ -470,6 +640,22 @@ $(function() $( '#first-run-wizard' ).hide(); } ); + // dialog confirmation + $( '#confirmation-dialog-ok' ).on('click', function(e) + { + $( '#confirmation-dialog' ).hide(); + dialog_action(); + } ); + $( '.confirmation-dialog-close' ).on('click', function(e) + { + $( '#confirmation-dialog' ).hide(); + } ); + $( '#confirmation-dialog' ).on('|click', function(e) + { + if( e.target.id == 'confirmation-dialog' ) + $( '#confirmation-dialog' ).hide(); + } ); + // click to nextcloud button $('#nextcloud-btn').set( '@href', window.location.protocol + '//' + window.location.hostname ); @@ -482,6 +668,7 @@ $(function() history.pushState(null, selectedID, "?app=dashboard"); } ); + // TODO unify repeating code // config button $( '#config-btn' ).on('click', function(e) { @@ -491,6 +678,15 @@ $(function() history.pushState(null, selectedID, "?app=config"); } ); + // backups button + $( '#backups-btn' ).on('click', function(e) + { + if ( lock ) return; + close_menu(); + switch_to_section( 'backups' ); + history.pushState(null, selectedID, "?app=backups"); + } ); + // language selection var langold = $( '#language-selection' ).get( '.value' ); $( '#language-selection' ).on( 'change', function(e) diff --git a/ncp-web/ncp-launcher.php b/ncp-web/ncp-launcher.php index 60f22e65..122a43ba 100644 --- a/ncp-web/ncp-launcher.php +++ b/ncp-web/ncp-launcher.php @@ -137,6 +137,21 @@ else if ( $_POST['action'] == "info" ) } // +// backups +// +else if ( $_POST['action'] == "backups" ) +{ + ob_start(); + include('backups.php'); + $backups_page = ob_get_clean(); + + // return JSON + echo '{ "token": "' . getCSRFToken() . '",'; // Get new token + echo ' "output": ' . json_encode($backups_page) . ' , '; + echo ' "ret": "0" }'; +} + +// // sidebar // else if ( $_POST['action'] == "sidebar" ) @@ -176,6 +191,34 @@ else if ( $_POST['action'] == "path-exists" ) } // +// del backup +// +else if ( $_POST['action'] == "del-bkp" ) +{ + $file = escapeshellarg($_POST['value']); + $ret = 1; + exec("sudo /home/www/ncp-backup-launcher.sh del $file", $out, $ret); + + // return JSON + echo '{ "token": "' . getCSRFToken() . '",'; // Get new token + echo ' "ret": "' . $ret . '" }'; +} + +// +// del snapshot +// +else if ( $_POST['action'] == "del-snap" ) +{ + $file = escapeshellarg($_POST['value']); + $ret = 1; + exec("sudo /home/www/ncp-backup-launcher.sh delsnp $file", $out, $ret); + + // return JSON + echo '{ "token": "' . getCSRFToken() . '",'; // Get new token + echo ' "ret": "' . $ret . '" }'; +} + +// // poweroff // else if ( $_POST['action'] == "poweroff" ) diff --git a/ncp-web/upload.php b/ncp-web/upload.php new file mode 100644 index 00000000..38b47cb7 --- /dev/null +++ b/ncp-web/upload.php @@ -0,0 +1,47 @@ +<?php +/// +// NextCloudPi Web Panel backend +// +// Copyleft 2019 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com> +// GPL licensed (see end of file) * Use at your own risk! +// +// More at https://nextcloudpi.com +/// + +include ('csrf.php'); +session_start(); + +// CSRF check +$token = isset($_POST['csrf_token']) ? $_POST['csrf_token'] : ''; +if ( empty($token) || !validateCSRFToken($token) ) + exit( '{ "output": "Unauthorized request. Try reloading the page" }' ); + +isset($_FILES['backup']) or exit( '{ "output": "no upload" }' ); + +$error=$_FILES['backup']['error']; +if ($error !== 0) + exit( '{ "output": "upload error ' . $error . '" }' ); + +$file_name = $_POST['csrf_token'] . basename($_FILES['backup']['name']); +$file_name = str_replace('/', '', $file_name); +$file_size = $_FILES['backup']['size']; +$file_tmp = $_FILES['backup']['tmp_name']; +$file_type = $_FILES['backup']['type']; + +preg_match( '/\.\./' , $file_name, $matches ) + and exit( '{ "output": "Invalid input" , "token": "' . getCSRFToken() . '" }' ); + +if($file_size === 0) + $errors[]='No file'; + +$extension = pathinfo($file_name, PATHINFO_EXTENSION); +if ($extension !== "tar" and $extension !== "gz") + exit( '{ "output": "invalid file" }' ); + +if (!move_uploaded_file($file_tmp, sys_get_temp_dir() . '/' . $file_name)) + exit('{ "output": "upload denied" }'); + +// return JSON +echo '{ "token": "' . getCSRFToken() . '",'; // Get new token +echo ' "ret": "0" }'; +?> @@ -22,7 +22,7 @@ install() { # NCP-CONFIG apt-get update - $APTINSTALL git dialog whiptail jq + $APTINSTALL git dialog whiptail jq file mkdir -p "$CONFDIR" "$BINDIR" # include option in raspi-config (only Raspbian) @@ -131,11 +131,39 @@ EOF cat > /home/www/ncp-launcher.sh <<'EOF' #!/bin/bash +grep -q '[\\&#;`|*?~<>^()[{}$&[:space:]]' <<< "$*" && exit 1 source /usr/local/etc/library.sh run_app $1 EOF chmod 700 /home/www/ncp-launcher.sh - echo "www-data ALL = NOPASSWD: /home/www/ncp-launcher.sh , /sbin/halt, /sbin/reboot" >> /etc/sudoers + + cat > /home/www/ncp-backup-launcher.sh <<'EOF' +#!/bin/bash +action="${1}" +file="${2}" +compressed="${3}" +grep -q '[\\&#;`|*?~<>^()[{}$&]' <<< "$*" && exit 1 +[[ "$file" =~ ".." ]] && exit 1 +[[ "${action}" == "chksnp" ]] && { + btrfs subvolume show "$file" &>/dev/null || exit 1 + exit +} +[[ "${action}" == "delsnp" ]] && { + btrfs subvolume delete "$file" || exit 1 + exit +} +[[ -f "$file" ]] || exit 1 +[[ "$file" =~ ".tar" ]] || exit 1 +[[ "${action}" == "del" ]] && { + [[ "$(file "$file")" =~ "tar archive" ]] || [[ "$(file "$file")" =~ "gzip compressed data" ]] || exit 1 + rm "$file" || exit 1 + exit +} +[[ "$compressed" != "" ]] && pigz="-I pigz" +tar $pigz -tf "$file" data &>/dev/null +EOF + chmod 700 /home/www/ncp-backup-launcher.sh + echo "www-data ALL = NOPASSWD: /home/www/ncp-launcher.sh , /home/www/ncp-backup-launcher.sh, /sbin/halt, /sbin/reboot" >> /etc/sudoers # NCP AUTO TRUSTED DOMAIN mkdir -p /usr/lib/systemd/system @@ -27,6 +27,8 @@ nc-init UFW nc-snapshot nc-snapshot-auto +nc-snapshot-sync +nc-restore-snapshot nc-audit nc-hdd-monitor nc-zram @@ -187,6 +189,45 @@ EOF # switch back to the apt LE version which letsencrypt &>/dev/null || install_app letsencrypt + # update launchers + apt-get update + apt-get install -y --no-install-recommends file + cat > /home/www/ncp-launcher.sh <<'EOF' +#!/bin/bash +grep -q '[\\&#;`|*?~<>^()[{}$&[:space:]]' <<< "$*" && exit 1 +source /usr/local/etc/library.sh +run_app $1 +EOF + chmod 700 /home/www/ncp-launcher.sh + + cat > /home/www/ncp-backup-launcher.sh <<'EOF' +#!/bin/bash +action="${1}" +file="${2}" +compressed="${3}" +grep -q '[\\&#;`|*?~<>^()[{}$&]' <<< "$*" && exit 1 +[[ "$file" =~ ".." ]] && exit 1 +[[ "${action}" == "chksnp" ]] && { + btrfs subvolume show "$file" &>/dev/null || exit 1 + exit +} +[[ "${action}" == "delsnp" ]] && { + btrfs subvolume delete "$file" || exit 1 + exit +} +[[ -f "$file" ]] || exit 1 +[[ "$file" =~ ".tar" ]] || exit 1 +[[ "${action}" == "del" ]] && { + [[ "$(file "$file")" =~ "tar archive" ]] || [[ "$(file "$file")" =~ "gzip compressed data" ]] || exit 1 + rm "$file" || exit 1 + exit +} +[[ "$compressed" != "" ]] && pigz="-I pigz" +tar $pigz -tf "$file" data &>/dev/null +EOF + chmod 700 /home/www/ncp-backup-launcher.sh + sed -i 's|www-data ALL = NOPASSWD: .*|www-data ALL = NOPASSWD: /home/www/ncp-launcher.sh , /home/www/ncp-backup-launcher.sh, /sbin/halt, /sbin/reboot|' /etc/sudoers + # remove redundant opcache configuration. Leave until update bug is fixed -> https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=815968 # Bug #416 reappeared after we moved to php7.2 and debian buster packages. (keep last) [[ "$( ls -l /etc/php/7.2/fpm/conf.d/*-opcache.ini | wc -l )" -gt 1 ]] && rm "$( ls /etc/php/7.2/fpm/conf.d/*-opcache.ini | tail -1 )" |