diff --git a/README.md b/README.md index 468f046..0528c25 100644 --- a/README.md +++ b/README.md @@ -1,4 +1 @@ -backup-commander -=============== - -Simple web-based backup GUI \ No newline at end of file +Add this line in the sudoers file (use visudo) and it must go AFTER the %sudo line www-data ALL=(ALL) NOPASSWD: /usr/bin/php /var/www/html/bin/run-bu-by-id.php 3rd-party backup-commander dom_persist eclipse-gwt help_dlang help.txt html_backup_UI html_stanhope res_builder stanhope_d tmp Test by switching user to www-data: su -s /bin/bash www-data cd /var/www/html php bin/run-bu-by-id.php 19 0 where 19 is the backup id and 0/1 is not-test/test Comment out /inc/secure.php in post_handler.php if the login is not required TODO: create an install script: -- currently all manually done First version on Las-server Pre-requesite to push onto pearcey.net Generate a database schema with default login add the sudo entry for www-data (or other webserver user) kick off the schedule runner at the install stage create the schedule runner in php. simple polling of the DB get passwords from BD create a user credential page: new user+password change own password but only if the login is enabled (in post_handler.php) diff --git a/html/bin/bu-n-err-18.txt b/html/bin/bu-n-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/bu-n-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/bu-n-err-19.txt b/html/bin/bu-n-err-19.txt new file mode 100644 index 0000000..062fc5d --- /dev/null +++ b/html/bin/bu-n-err-19.txt @@ -0,0 +1 @@ +ID: 19 diff --git a/html/bin/bu-n-op-19.txt b/html/bin/bu-n-op-19.txt new file mode 100644 index 0000000..f566b18 --- /dev/null +++ b/html/bin/bu-n-op-19.txt @@ -0,0 +1,12 @@ +ID: 19 +sending incremental file list +html/bin/ +html/bin/bu-n-err-19.txt +html/bin/bu-n-op-19.txt +html/bin/bu-x-op-19.txt +html/bin/data.db +html/js/ +html/js/main.js + +sent 33,631 bytes received 127 bytes 67,516.00 bytes/sec +total size is 226,992 speedup is 6.72 diff --git a/html/bin/bu-t-err-18.txt b/html/bin/bu-t-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/bu-t-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/bu-t-op-18.txt b/html/bin/bu-t-op-18.txt new file mode 100644 index 0000000..8a74739 --- /dev/null +++ b/html/bin/bu-t-op-18.txt @@ -0,0 +1,51 @@ +/*!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.6.18-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: johntest +-- ------------------------------------------------------ +-- Server version 10.6.18-MariaDB-0ubuntu0.22.04.1 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `BULIST` +-- + +DROP TABLE IF EXISTS `BULIST`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `BULIST` ( + `BUID` int(11) NOT NULL AUTO_INCREMENT, + `BUName` varchar(9) DEFAULT NULL, + PRIMARY KEY (`BUID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `BULIST` +-- + +LOCK TABLES `BULIST` WRITE; +/*!40000 ALTER TABLE `BULIST` DISABLE KEYS */; +/*!40000 ALTER TABLE `BULIST` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-10-03 16:13:05 diff --git a/html/bin/bu-x-op-19.txt b/html/bin/bu-x-op-19.txt new file mode 100644 index 0000000..9fcf8b5 --- /dev/null +++ b/html/bin/bu-x-op-19.txt @@ -0,0 +1 @@ +html/bin/rsync-n-op-19.txt diff --git a/html/bin/data.db b/html/bin/data.db new file mode 100644 index 0000000..e829a56 --- /dev/null +++ b/html/bin/data.db Binary files differ diff --git a/html/bin/rsync-n-err-18.txt b/html/bin/rsync-n-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/rsync-n-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/rsync-n-err-19.txt b/html/bin/rsync-n-err-19.txt new file mode 100644 index 0000000..062fc5d --- /dev/null +++ b/html/bin/rsync-n-err-19.txt @@ -0,0 +1 @@ +ID: 19 diff --git a/html/bin/rsync-n-op-19.txt b/html/bin/rsync-n-op-19.txt new file mode 100644 index 0000000..20ee8de --- /dev/null +++ b/html/bin/rsync-n-op-19.txt @@ -0,0 +1,13 @@ +ID: 19 +sending incremental file list +html/bin/ +html/bin/data.db +html/bin/rsync-n-err-19.txt +html/bin/rsync-t-err-19.txt +html/bin/rsync-t-op-19.txt +html/bin/rsync-x-op-19.txt +html/inc/ +html/inc/bu-common.php + +sent 27,025 bytes received 146 bytes 54,342.00 bytes/sec +total size is 217,616 speedup is 8.01 diff --git a/html/bin/rsync-t-err-18.txt b/html/bin/rsync-t-err-18.txt new file mode 100644 index 0000000..3cfa03a --- /dev/null +++ b/html/bin/rsync-t-err-18.txt @@ -0,0 +1 @@ +ID: 18 diff --git a/html/bin/rsync-t-err-19.txt b/html/bin/rsync-t-err-19.txt new file mode 100644 index 0000000..062fc5d --- /dev/null +++ b/html/bin/rsync-t-err-19.txt @@ -0,0 +1 @@ +ID: 19 diff --git a/html/bin/rsync-t-op-18.txt b/html/bin/rsync-t-op-18.txt new file mode 100644 index 0000000..42f1c56 --- /dev/null +++ b/html/bin/rsync-t-op-18.txt @@ -0,0 +1,51 @@ +/*!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.6.18-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: johntest +-- ------------------------------------------------------ +-- Server version 10.6.18-MariaDB-0ubuntu0.22.04.1 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `BULIST` +-- + +DROP TABLE IF EXISTS `BULIST`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `BULIST` ( + `BUID` int(11) NOT NULL AUTO_INCREMENT, + `BUName` varchar(9) DEFAULT NULL, + PRIMARY KEY (`BUID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `BULIST` +-- + +LOCK TABLES `BULIST` WRITE; +/*!40000 ALTER TABLE `BULIST` DISABLE KEYS */; +/*!40000 ALTER TABLE `BULIST` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-10-03 15:59:07 diff --git a/html/bin/rsync-t-op-19.txt b/html/bin/rsync-t-op-19.txt new file mode 100644 index 0000000..6f9197e --- /dev/null +++ b/html/bin/rsync-t-op-19.txt @@ -0,0 +1,12 @@ +ID: 19 +sending incremental file list +html/bin/ +html/bin/data.db +html/bin/rsync-t-err-19.txt +html/bin/rsync-t-op-19.txt +html/bin/rsync-x-op-19.txt +html/inc/ +html/inc/bu-common.php + +sent 1,238 bytes received 43 bytes 2,562.00 bytes/sec +total size is 217,366 speedup is 169.68 (DRY RUN) diff --git a/html/bin/rsync-x-op-19.txt b/html/bin/rsync-x-op-19.txt new file mode 100644 index 0000000..9fcf8b5 --- /dev/null +++ b/html/bin/rsync-x-op-19.txt @@ -0,0 +1 @@ +html/bin/rsync-n-op-19.txt diff --git a/html/bin/run-bu-by-id.php b/html/bin/run-bu-by-id.php new file mode 100644 index 0000000..7401295 --- /dev/null +++ b/html/bin/run-bu-by-id.php @@ -0,0 +1,25 @@ + the name/path of this script +$id = $argv[1]; +$isTest = $argv[2]; + +$str_err = run_backup_block( $id, $isTest=='1' ); + +/* + * Since this script is used from within the shell, we loose return values. Therefore, any error + * recieved from the previous call will be echoed to the console. It shall have the string + * "Error:" prepended. + * + * The shell exec will pickup the echoed output and test for the "Error" string. + * + * The output is silent if all went well. + */ +if($str_err!='') echo "Error: $str_err"; + +?> diff --git a/html/css/main.css b/html/css/main.css new file mode 100644 index 0000000..9c6b9e2 --- /dev/null +++ b/html/css/main.css @@ -0,0 +1,188 @@ +* { + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + } + + body, html { + padding: 3px 3px 3px 3px; + background-color: #D8DBE2; + font-family: Ubuntu, Verdana, sans-serif; + font-size: 11pt; + text-align: center; + } + + div.main_page { + position: relative; + display: table; + + width: 1000px; + + margin-bottom: 3px; + margin-left: auto; + margin-right: auto; + padding: 0px 0px 0px 0px; + + border-width: 2px; + border-color: #212738; + border-style: solid; + + background-color: #FFFFFF; + + text-align: center; + } + + div.content_section_text { + padding: 4px 8px 4px 8px; + + color: #000000; + font-size: 100%; + } + + div.content_section_text pre { + margin: 8px 0px 8px 0px; + padding: 8px 8px 8px 8px; + + border-width: 1px; + border-style: dotted; + border-color: #000000; + + background-color: #F5F6F7; + + font-style: italic; + } + + div.content_section_text p { + margin-bottom: 6px; + } + + div.content_section_text ul, div.content_section_text li { + padding: 4px 8px 4px 16px; + } + + div.section_header { + padding: 3px 6px 3px 6px; + + background-color: #8E9CB2; + + color: #FFFFFF; + font-weight: bold; + font-size: 112%; + text-align: center; + margin-top: 10px; + } + + div.content_section_text a { + text-decoration: none; + font-weight: bold; + } + + + div.content_section_text a:link, + div.content_section_text a:visited, + div.content_section_text a:active { + background-color: #DCDFE6; + color: #0e3f2b; + } + + div.content_section_text a:hover { + background-color: #0e3f2b; + color: #DCDFE6; + } + +.tblcenter { + width: 100%; + margin-left: 10px; + margin-right: 10px; + text-align: left; +} + +.flex_header{ + display: flex; + justify-content: space-between; + align-items: stretch; + background-color: #0e3f2b; + border: solid lightgray 1px; + border-bottom: solid gray 1px; + min-height: 50px; + margin-bottom: 10px; +} + +.shb_Title img { + margin: 3px 3px 3px 5px; +} + +.flex_header .shb_Title { + display: flex; + flex-direction: row; + align-items: center; + color: #cfa949; + font-size: 18px; + font-weight: bold; + font-family: Helvetica, Arial, sans-serif; + margin-left: 0px; +} + +.flex_header .shb_TitleRHS { + display: flex; + flex-direction: row; + align-items: center; /*align-items is perpendicular to the flex-direction*/ + color: #c9bda2; +} + +.page_inner{ + margin-left: 10px; + margin-right: 10px; +} + +.btn_mse:hover { + cursor: pointer; +} + +.btn_exec{ + background-color: #04AA6D; + border: none; + color: white; + padding: 5px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 12px; + border-radius: 8px; +} +.btn_small { + font-size: 10px; + border-radius: 6px; + padding: 2px 5px 2px 5px; +} + +.td_label_bkup{ + text-align: right; + padding-right: 15px; +} + +table tr.high-row-style td { + padding-top: 15px; +} + +table tr td.td_icon { + width: 35px; +} + +.two-col-panel { + padding-top: 15px; + text-align: left; + display: grid; + grid-template-columns: 200px auto; +} + +.two-col-panel > div { + margin-top: 5px; +} + +.two-col-panel input { + width: 300px; +} + +.two-col-panel textarea { + width: 300px; +} diff --git a/html/img/edit-icon-24x24.png b/html/img/edit-icon-24x24.png new file mode 100644 index 0000000..7777001 --- /dev/null +++ b/html/img/edit-icon-24x24.png Binary files differ diff --git a/html/img/edit-icon-32x32.png b/html/img/edit-icon-32x32.png new file mode 100644 index 0000000..cc0151c --- /dev/null +++ b/html/img/edit-icon-32x32.png Binary files differ diff --git a/html/img/edit-icon-L.png b/html/img/edit-icon-L.png new file mode 100644 index 0000000..b2ed893 --- /dev/null +++ b/html/img/edit-icon-L.png Binary files differ diff --git a/html/img/green-tick.png b/html/img/green-tick.png new file mode 100644 index 0000000..b86cdd8 --- /dev/null +++ b/html/img/green-tick.png Binary files differ diff --git a/html/img/red-cross.png b/html/img/red-cross.png new file mode 100644 index 0000000..b965b00 --- /dev/null +++ b/html/img/red-cross.png Binary files differ diff --git a/html/img/trash-bin-red-24x24.jpg b/html/img/trash-bin-red-24x24.jpg new file mode 100644 index 0000000..0dd4304 --- /dev/null +++ b/html/img/trash-bin-red-24x24.jpg Binary files differ diff --git a/html/img/trash-bin-red-24x24.png b/html/img/trash-bin-red-24x24.png new file mode 100644 index 0000000..bdec232 --- /dev/null +++ b/html/img/trash-bin-red-24x24.png Binary files differ diff --git a/html/img/uk-green.png b/html/img/uk-green.png new file mode 100644 index 0000000..dab2252 --- /dev/null +++ b/html/img/uk-green.png Binary files differ diff --git a/html/inc/.htaccess b/html/inc/.htaccess new file mode 100644 index 0000000..15f487c --- /dev/null +++ b/html/inc/.htaccess @@ -0,0 +1,4 @@ + +Order Allow,Deny +Deny from all + diff --git a/html/inc/btn_run_bu.php b/html/inc/btn_run_bu.php new file mode 100644 index 0000000..170ef10 --- /dev/null +++ b/html/inc/btn_run_bu.php @@ -0,0 +1,52 @@ +0 ){ + if( substr( $output[0], 0, 6) == 'Error:' ){ + error_log( $output[0] ); + return array('error', $output[0] ); + } + } + + if($testrun!='false'){ + //a test run waits for the command to finish, so the output can be collected and sent back now + for($i=0;$i<2;$i++){ + $fn = __DIR__."/../bin/".getFilename_out( $buid, $bIsTest, $i==0 ); + $output[] = file_get_contents($fn); + } + + return array_merge( array('done'), $output ); + } + + //error_log( "Backup kicked off for ID($buid): isTest=$isTest"); + + return array_merge( array('running'), $output ); +} + + +?> diff --git a/html/inc/bu-common.php b/html/inc/bu-common.php new file mode 100644 index 0000000..508fa44 --- /dev/null +++ b/html/inc/bu-common.php @@ -0,0 +1,234 @@ +query("SELECT * FROM BULIST where BUID=$id"); + $row = $results->fetchArray(); + + if($row){ + return array( $row['BUName'], $row['Dir_Src'], $row['Dir_Dest'], $row['Files_Ex'], $row['BuType'], $row['BuRunning'], $row['BuError'], $row['LastRunDt'] ); + } + +} + +/** + * Set (unset) the running datbase flag. Return true on success. The flag is + * unaltered if running in test mode. + */ +function set_bu_running( $id, bool $setit, bool $bu_test ){ + + if($bu_test) return true; + + $db = new SQLite3( __DIR__.'/../bin/data.db' ); + + $setit_int = $setit?1:0; + return $db->exec("update BULIST set BuRunning=$setit_int where BUID=$id"); +} + +/** + * Set the error string in the database. + */ +function set_bu_error( $buid, $str_errs ){ + $db = new SQLite3( __DIR__.'/../bin/data.db' ); + if($str_errs==''){ + return $db->exec("update BULIST set BuError = NULL where BUID=$buid"); + } + $p = $db->prepare("update BULIST set BuError=:str_errs where BUID=:buid"); + $p->bindValue(':str_errs', $str_errs ); + $p->bindValue(':buid', $buid ); + $p->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } +} + +/** + * + */ +function set_last_run_date( $buid, $str_dt ){ + $db = new SQLite3( __DIR__.'/../bin/data.db' ); + $p = $db->prepare("update BULIST set LastRunDt=:str_dt where BUID=:buid"); + $p->bindValue(':str_dt', $str_dt ); + $p->bindValue(':buid', $buid ); + $p->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } +} + + +/** + * Run the backup process for the given backup ID. + * + * Returns an error string on failure. + */ +function run_backup_block( $buid, $bu_test=true ){ + + $row = get_bu_item( $buid ); + + if( !$bu_test && $row[5]==1){ + return "Backup for item($buid) is already running\n"; + } + + $bu_name = $row[0]; + $bu_src = $row[1]; + $bu_dest = $row[2]; + $exFiles = $row[3]; + $bu_type = $row[4]; + $bu_rnng = $row[5]; + + // NOTE: + // using pcntl_fork() was a waste of time because the parent always waited for the child to finish. + // so we rely on bash & + + if($bu_type==1 ){ + return run_backup_DB( $bu_test, $buid, $bu_src, $bu_dest ); + } + + return run_backup_F( $bu_test, $buid, $bu_src, $bu_dest, $exFiles ); +} + +function run_backup_F( $bu_test, $buid, $bu_src, $bu_dest, $exFiles ){ + + if(!set_bu_running( $buid, true, $bu_test )){ + return "Could not set the DB flag"; + } + + if($bu_test){ + $flags = '-anv'; // n is the dry run indicator + }else{ + $flags = '-av'; + set_bu_error( $buid, '' ); //clear any previous error + set_last_run_date( $buid, gmdate("Y-m-d H:i:s") ); + } + + $fn_o = getFilename_out( $buid, $bu_test, false ); + $fn_e = getFilename_out( $buid, $bu_test, true ); + + // create the output files with the ID in the first line + exec("echo 'ID: $buid' > bin/$fn_o"); + exec("echo 'ID: $buid' > bin/$fn_e"); + + $str_exPaths = ''; + if(trim( $exFiles )!=''){ + $fn_x = getFilename_tmp( $buid, 'x' ); + file_put_contents( "bin/$fn_x", $exFiles ); + $str_exPaths = "--exclude-from=bin/$fn_x"; + } + + // bash command + //rsync -a --exclude-from=to-exclude.txt $PATH_SRC $BKUP_DEST + //rsync -anv --exclude-from=to-exclude.txt $PATH_SRC $BKUP_DEST <-- for testing + $command = "rsync $flags $str_exPaths $bu_src $bu_dest 2>> bin/$fn_e 1>> bin/$fn_o"; + + $output = array(); + $result_code = null; + if( exec( $command, $output, $result_code )===FALSE){ + set_bu_running( $buid, false, $bu_test ); + return "rsync failed"; + } + set_bu_running( $buid, false, $bu_test ); + + // can we find any error strings in the error file? + $str_errs = file_get_contents( "bin/$fn_e" ); + $pos = strpos($str_errs, 'error' ); + if($pos===FALSE){ + $pos = strpos($str_errs, 'failed' ); + } + if($pos===FALSE) return; + + //place it in the database + set_bu_error( $buid, $str_errs ); + +} + + +/** + * Very similar to the file version. The common code is running the bach script. + */ +function run_backup_DB( $bu_test, $buid, $bu_src, $bu_dest ){ + + if(!set_bu_running( $buid, true, $bu_test )){ + return "Could not set the DB flag"; + } + + if(!is_dir($bu_dest) ){ + return "Destination ($bu_dest) is not a directory"; + } + + if($bu_test){ + $fn_o = getFilename_out( $buid, $bu_test, false ); + $bu_dest = "bin"; + }else{ + $fn_o = "$bu_src.sql"; + set_bu_error( $buid, '' ); //clear any previous error + set_last_run_date( $buid, gmdate("Y-m-d H:i:s") ); + } + + // create the error files with the ID in the first line + $fn_e = getFilename_out( $buid, $bu_test, true ); + exec("echo 'ID: $buid' > bin/$fn_e"); + + // bash command + //mysqldump johntest > /home/johnp/tmp/dbs/johntest.sql + + $command = "mysqldump $bu_src 2>> bin/$fn_e 1>> $bu_dest/$fn_o"; + + $output = array(); + $result_code = null; + if( exec( $command, $output, $result_code )===FALSE){ + set_bu_running( $buid, false, $bu_test ); + return "mysqldump failed"; + } + set_bu_running( $buid, false, $bu_test ); + + // can we find any error strings in the error file? + $str_errs = file_get_contents( "bin/$fn_e" ); + $pos = strpos($str_errs, 'error' ); + if($pos===FALSE){ + $pos = strpos($str_errs, 'failed' ); + } + if($pos===FALSE) return; + + //place it in the database + set_bu_error( $buid, $str_errs ); + +} + +function getFilename_out( $buid, bool $bu_test, bool $isErr=false ){ + return getFilename_tmp( $buid, $bu_test?'t':'n', $isErr? 'err': 'op' ); +} + +/** + * Create an output file for the backup (forked) process to use. These are essentually + * temporary files which may be deleted after the back process completes. + * + * buid: backup record id + * + * fn_type: + * t: for test files + * n: for non-test files + * x: for file exclusion list + * + * fn_stream: + * op: output stream + * err: error stream + */ +function getFilename_tmp( $buid, $fn_type='t', $fn_stream='op' ){ + return "bu-$fn_type-$fn_stream-$buid.txt"; +} + +?> diff --git a/html/inc/bu_list_content.php b/html/inc/bu_list_content.php new file mode 100644 index 0000000..b5f9a2e --- /dev/null +++ b/html/inc/bu_list_content.php @@ -0,0 +1,111 @@ +query('SELECT BUID, BuRunning FROM BULIST'); + while ($row = $results->fetchArray()) { + array_push( $bu_state, array( $row['BUID'], $row['BuRunning'] ) ); + } + return $bu_state; +} + + +/** + * Returns the main backup list page. + */ +function getbu_list_content(){ + + $db = new SQLite3('bin/data.db', SQLITE3_OPEN_READONLY ); + + $html_rows=''; + $results = $db->query('SELECT * FROM BULIST'); + while ($row = $results->fetchArray()) { + //var_dump($row); + + $view_type = 'edit-file-bu'; + if($row['BuType']==1){ + $view_type = 'edit-db-bu'; + } + + $html_row = ''; + + $html_row .= ''.$row['BUID'].''; + $html_row .= ""; // edit icon + $html_row .= ""; // trash icon + $html_row .= "{$row['BUName']}"; + $but = $row['BuType']==1?'DB':'F'; + $html_row .= "$but"; + + if($row['BuError']!=''){ + $html_row .= "!! {$row['LastRunDt']}"; + }else{ + $html_row .= "{$row['LastRunDt']}"; + } + + if( $row['BU_REPEAT']=='N' ){ + $html_row .= ""; + }else{ + $html_row .= " {$row['BU_REPEAT']}"; + } + + if($row['BuRunning']){ + $html_row .= ''; + }else{ + $html_row .= ""; + } + + $html_row .= ''; + + $html_rows .= $html_row; + } + +$content = <<<'EOD' + +
Backup Config List
+ +
+
+ + + + + + + + + + + + + --INSERT-ROWS-- + + + + + + + + + + + + + + + + +
IDNameTypeLast Run (UTC)ScheduledRunning
Database backup
Files backup
+
+EOD; + + return str_replace('--INSERT-ROWS--', $html_rows, $content); + +} + +?> diff --git a/html/inc/common.php b/html/inc/common.php new file mode 100644 index 0000000..240ad08 --- /dev/null +++ b/html/inc/common.php @@ -0,0 +1,352 @@ +getTraceAsString(); + } +} + +/** + * e.g. split + * ../db/stanhopetest/img/img_52548 + * into + * [ ../db/stanhopetest/img/ , img_52548 ] + * + */ +function split_file_path( $ffn ){ + $fn = basename( $ffn ); // img_52548 + //$fp = substr( $ffn, 0, strlen($ffn) - strlen($fn) ); + $fp = dirname( $ffn ) . '/'; + return [ $fp, $fn ]; +} + +/** + * Returns true if it is possible to create this directory on the file system. + * Includes the possibility of multiple directories. + */ +function is_creatable_dir( $dirs ){ + $dir = $dirs; + while(!file_exists($dir)){ + $dir = dirname( $dir ); + } + return is_writable($dir); +} + +/** + * Function to make directories and actually SET the permissions + * https://www.php.net/manual/en/function.chmod.php + * + * $perms is an octal number + * e.g. mkdirs( $dir, 02770 ); //770 and g+s + */ +function mkdirs( $dirs, $perms ){ + $dir = $dirs; + while(!file_exists($dir)){ + $dir = dirname( $dir ); + } + $rootDir = $dir; + if(!file_exists($dirs)) mkdir( $dirs, 0750, true ); //perms not always set + $dir = $dirs; + while( $dir!=$rootDir) { + chmod( $dir, $perms ); + $dir = dirname( $dir ); + } +} + +/** + * Return the contents of a url as a string using the curl functions. + */ +function getContentsFromUrl( $url ){ + + $ch = curl_init( $url ); + //$fp = fopen("example_homepage.txt", "w"); + $fp = tmpfile(); + + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_HEADER, 0); + + curl_exec($ch); + $c_err = curl_error($ch); + curl_close($ch); + if($c_err) { + error_log( $c_err ); + fclose($fp); + return; + } + + $f_path = stream_get_meta_data($fp)['uri']; + $rtn = file_get_contents( $f_path ); + fclose($fp); + + return $rtn; +} + +/** + * Set the given key-value pair. + * + * Returns the record ID. + */ +function params_set( $key, $val ) { + + $rec = R::findOne( 'params', 'pkey=?', [$key] ); + if($rec) { + $rec->pval = $val; + R::store( $rec ); + return $rec->id; + } + + $rec = R::dispense( 'params' ); + $rec->pkey = $key; + $rec->pval = $val; + return R::store( $rec ); +} + +function params_get_date( $key ){ + return strtotime( params_get($key) ); +} + +/** + * Returns the value for the given key. Returns null if no such key exists. + */ +function params_get( $key ){ + + $rec = R::findOne( 'params', 'pkey=?', [$key] ); + if(!$rec) return null; + return $rec->pval; +} + +/** + * A novel way of creating a unique ID each time a call is made. The new ID is + * returned. + * + * e.g. + * This can be used to give a client which might want to create temporary unique IDs. It is + * called once per client refresh and used to ensure uniqueness of certain CSS data points. + */ +function params_unique( ) { + + $rec = R::dispense( 'params' ); + $rec->pkey = 'unique'; + $id = R::store( $rec ); + R::exec( "delete from params where id<$id and pkey='unique'" ); + return $id; +} + +/** + * Return a string representation for the given dom node suitable for debugging. + */ +function getNodeString( $rmElem ){ + + if($rmElem==null) return "Element is null"; + $str=""; + if( $rmElem->nodeType==XML_ELEMENT_NODE){ + $str .= $rmElem->nodeName; + $a = $rmElem->getAttribute("shb_compid"); + if($a){ + $str .= " shb_compid=$a"; + } + }else{ + $str .= 'type = '.$rmElem->nodeType; + } + return $str; +} + +/** + * Simple logger to write to a file. Using php error_log distorts all crlf pairs. This + * is a pain when you're trying to preserve them in html. + */ +function mylog( $msg ){ + + global $logging_on; + + if(!$logging_on) return; + $p = __DIR__ . "/../../shb.log"; + $h = fopen( $p, "a"); + if($h===false) return; + fwrite( $h, "$msg\n"); + fclose($h); + +} + +function generateNonce( $digitCount = 12 ){ + // e.g. 422466a05f24b09c978fa1f6 + return bin2hex( random_bytes($digitCount) ); +} + +/** + * Generate a randon username starting with a letter and containing only letters and numbers. + */ +function generateRndUserName( $digitCount = 10 ){ + $l1 = generateRndStrUsing( 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 1 ); + return $l1 . generateRndStrUsing( BASE64_DIGITS, $digitCount-1 ); +} + +/** + * Generate a randon password with letters and numbers and some funny stuff. + */ +function generateRndPwd( $digitCount = 10 ){ + return generateRndStrUsing( '0123456789_!"£$%^&*()_-|\?/<>#~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', $digitCount ); +} + +/** + * Generate a randon string of asc char. Default consisting of only [0-9][a-z][A-Z] + */ +function generateRndStrUsing( $using=BASE64_DIGITS, $digitCount = 8 ){ + + $str = ''; + $maxlen = strlen($using); + for($i=0; $i<$digitCount; $i++){ + $iv = random_int(0, $maxlen-1); + $str .= mb_substr( $using, $iv, 1); //use mb_substr because £ is not and asc char! + } + return $str; +} + + +/** + * Remove all html from the given string returning just the text content. + */ +function removeHtml( $str ){ + //txt = txt.replace(/
/gi, '\n'); + return preg_replace( '/<\/?[^>]+>/i', '', $str ); +} + +function getHtmlOkAsString( $data ){ + $data = json_encode( $data ); + return "[\"OK\", $data ]"; +} + +function sendHtmlOk( $data="" ){ + echo getHtmlOkAsString( $data ); +} + +/** + * An effort to rectify my previously ill-thought out return encoding. You do not + * need to encode the data before calling this method. Any arrays will be json encoded. + */ +function sendHtmlOk_WithData( $data ){ + $data = json_encode( $data ); + echo "[\"OK\", $data ]"; +} + +function getHtmlErrorAsString( $strErrDesc = "--blank--" ){ + $data = json_encode( $strErrDesc ); + return "[\"Error\", $data]"; +} + +function sendHtmlError( $strErrDesc = "--blank--" ){ + $rtn = getHtmlErrorAsString( $strErrDesc ); + mylog( $rtn ); + echo $rtn; +} + +/** + * + */ +function getAttForPath( $name, $prefPath, $path){ + return "$name = \"$prefPath/$path\""; +} + +global $g_pub_path; + +/** + * If the browser=true, the file returned will be correct for adding to an href or src attribute so that the + * browser will find the link. + */ +function getDirForType( $filetype='html', $browser=false, $userid=-1 ){ + + global $g_pub_path; + + if($g_pub_path==null){ + $g_pub_path = params_get('pub_path'); + if($g_pub_path==null){ + throw new Exception("Publishing now requires a valid key('pub_path') in the params table for the publishing output path"); + } + + if(str_ends_with( $g_pub_path, '/') ) $g_pub_path = substr($g_pub_path, 0, strlen($g_pub_path)-1 ); + + //ensure all the directories exist + createUserDir( 'img', $userid ); + createUserDir( 'css', $userid ); + createUserDir( 'html', $userid ); + createUserDir( 'js', $userid ); + + } + + return getDirForType_( $filetype, $browser, $userid ); + +} + +function createUserDir( $dName, $userid ){ + + if(!file_exists(getDirForType_( $dName, false, $userid ))) { + $dir = getDirForType_( $dName, false, $userid ); + error_log("common.php: createUserDir: $dir"); + mkdir( $dir, 0770, true); + } +} + +/** + * Part of the new path handling system is to sepatate out path names from real files +*/ +function mkdirIfNotExists( $d ){ + if(!file_exists( $d ) ) { + error_log("common.php: createUserDir: $d"); + mkdir( $d, 0770, true ); + } +} + +/** + * For the moment, call getDirForType and not this function. In time, the setup routine will make the directories + * and eliminate the need for the check during a user session. + * + * If the browser flag is set, we assuming a document root sub-dir. But in time, this will need to also cater for + * a site serving the g_pub_path as root (wrt the browser). + * + */ +function getDirForType_( $filetype, $browser, $userid ){ + + global $g_pub_path; + $strPath = $g_pub_path; + + + if($userid>0){ + $strPath .= "_dbg_$userid"; + } + + switch($filetype){ + + case 'img': + if($browser) return "/$strPath/img/"; + return fxdPth( "$strPath/img/" ); + + case 'js': + if($browser) return "/$strPath/js/"; + return fxdPth( "$strPath/js/" ); + + case 'css': + if($browser) return "/$strPath/css/"; + return fxdPth( "$strPath/css/" ); + + case 'html': + default: + if($browser) return "/$strPath/"; + return fxdPth( "$strPath/" ); + + } +} + + +//NO white-space diff --git a/html/inc/delete-file-bu.php b/html/inc/delete-file-bu.php new file mode 100644 index 0000000..b24d99c --- /dev/null +++ b/html/inc/delete-file-bu.php @@ -0,0 +1,17 @@ +prepare('delete from BULIST where BUID=:buid'); + $db_st->bindValue(':buid', $id ); + $db_st->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } + +} + +?> diff --git a/html/inc/edit-sh-bu.php b/html/inc/edit-sh-bu.php new file mode 100644 index 0000000..920a1c7 --- /dev/null +++ b/html/inc/edit-sh-bu.php @@ -0,0 +1,111 @@ +prepare('SELECT * FROM BULIST where BUID=:buid'); + $db_st->bindValue(':buid', $db_id); + $results = $db_st->execute(); + $row = $results->fetchArray(); + if ( $row===FALSE ) { + return "Item $db_id not found"; + } + + $bun = $row['BUName']; + $bus = $row['Dir_Src']; + $bu_dt = $row['BU_DATE']; + if($bu_dt==''){ + $bu_dt = date("Y-m-d"); + } + $bu_t = $row['BU_TIME']; + if($bu_t==''){ + $bu_t = '00:00'; + } + $bu_r = $row['BU_REPEAT']; + + switch($bu_r){ + case 'D': + $id_checked = 'id="sched_ip_daily"'; + break; + case 'W': + $id_checked = 'id="sched_ip_weekly"'; + break; + case 'M': + $id_checked = 'id="sched_ip_monthly"'; + break; + case 'N': + default: + $id_checked = 'id="sched_ip_none"'; + } + +$content = <<<'EOD' + +
Edit Backup Schedule
+ +
+ +
+ +
ID
+
$db_id
+ +
Backup Name
+
$bun
+ +
Data Source
+
$bus
+ +
Next Run Date/Time
+
+ +
+
+ +
Repeat
+
+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+EOD; + + $content = str_replace($id_checked, $id_checked.' checked ', $content ); + $content = str_replace('$bus', $bus, $content); + $content = str_replace('$bun', $bun, $content); + $content = str_replace('$bu_dt', $bu_dt, $content); + $content = str_replace('$bu_t', $bu_t, $content); + return str_replace('$db_id', $db_id, $content); +} + +function save_bu_schedule( $postVars ){ + + //error_log( $postVars['id'] ); + + $db = new SQLite3('bin/data.db' ); + + $db_st = $db->prepare('UPDATE BULIST SET BU_DATE=:bud, BU_TIME=:but, BU_REPEAT=:bur where BUID=:buid'); + $db_st->bindValue(':buid', $postVars['id'], SQLITE3_INTEGER ); + $db_st->bindValue(':bud', $postVars['sched_date'] ); + $db_st->bindValue(':but', $postVars['sched_time'] ); + $db_st->bindValue(':bur', $postVars['sched_period'] ); + $db_st->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } + +} + +?> diff --git a/html/inc/login.php b/html/inc/login.php new file mode 100644 index 0000000..9304f5c --- /dev/null +++ b/html/inc/login.php @@ -0,0 +1,45 @@ +Login
+ +
+ +
+ +
Login Name
+
+ +
Password
+
+ + +
+
+ +
+ +
+ +
+EOD; + + //$content = str_replace('$header_cont', $header_cont, $content); + return $content; +} + +function authenticate( $username, $hashed_pwd_client, $svr_nonce ){ + + //TODO: + // get the username and password from somewhere. + $hashed_pwd_correct = hash('sha256', 'biggles'.$svr_nonce ); + + return $hashed_pwd_correct == $hashed_pwd_client; +} + + +?> diff --git a/html/inc/new-file-bu.php b/html/inc/new-file-bu.php new file mode 100644 index 0000000..0c6951d --- /dev/null +++ b/html/inc/new-file-bu.php @@ -0,0 +1,172 @@ +0){ + $id_line = "
ID
$db_id
"; + $item_data = get_bu_item( $db_id ); // [ BUName, Dir_Src, Dir_Dest, Files_Ex, Bu_Type, BuRunning, BuError, LastRunDt ] + + $bu_name_value = "value='{$item_data[0]}'"; + $bu_src_value = "value='{$item_data[1]}'"; + $bu_dest_value = "value='{$item_data[2]}'"; + $bu_ex_files = $item_data[3]; + + $bu_error_list = str_replace( "\n", '
', $item_data[6]); + $bu_error_list = "
Errors
$bu_error_list
"; + + $lastRunTime = "
Last Run (UTC)
{$item_data[7]}
"; + + } + + if($bDbStyle){ + if($db_id>0){ + $header_cont = '
Edit Database Backup Item
'; + $btn_new_bu = "btn_edit_bu_db','$db_id"; + + }else{ + $header_cont = '
Create New Database Backup Item
'; + $btn_new_bu = 'btn_new_bu_db'; + + } + $lab1_cont = '
Database name
'; + + }else{ + if($db_id>0){ + $header_cont = '
Edit File Backup Item
'; + $btn_new_bu = "btn_edit_bu_file','$db_id"; + + }else{ + $header_cont = '
Create New File Backup Item
'; + $btn_new_bu = 'btn_new_bu_file'; + + } + + $lab1_cont = '
Source File/Dir
'; + $exfiles_cont = "
Exclude Files
"; + } + +$content = <<<'EOD' + + $header_cont + +
+ +
+ + $id_line + +
Backup Name
+
(optional)
+ + $lab1_cont +
+ +
Destination Dir
+
+ + $exfiles_cont + +
+
+ + + +
+ +
Test output
+
+ + $lastRunTime + $bu_error_list + +
+ +
+EOD; + + $content = str_replace('$bu_error_list', $bu_error_list, $content); + $content = str_replace('$lastRunTime', $lastRunTime, $content); + $content = str_replace('btn_run_bu\'', "btn_run_bu', $db_id", $content); + $content = str_replace('$bu_name_value', $bu_name_value, $content); + $content = str_replace('$bu_src_value', $bu_src_value, $content); + $content = str_replace('$bu_dest_value', $bu_dest_value, $content); + + $content = str_replace('$id_line', $id_line, $content); + $content = str_replace('$btn_new_bu', $btn_new_bu, $content); + $content = str_replace('$exfiles_cont', $exfiles_cont, $content); + $content = str_replace('$lab1_cont', $lab1_cont, $content); + $content = str_replace('$header_cont', $header_cont, $content); + return $content; +} + +/** + * Both inserts as well as edits a single backup item. + */ +function save_bu_item( $postVars, $bu_id=0 ){ + + //error_log( "save_bu_item id = $bu_id, new_bu_dest = {$postVars['new_bu_dest']}" ); + + $db = new SQLite3('bin/data.db' ); + $db_st = null; + if( $postVars['new_bu_is_dbType'] ){ + if($bu_id==0){ + $db_st = $db->prepare('INSERT INTO BULIST ( BUName, Dir_Src, Dir_Dest, BuType ) values( :bun, :bus, :bud, 1 )'); + }else{ + $db_st = $db->prepare('update BULIST set BUName=:bun, Dir_Src=:bus, Dir_Dest=:bud where BUID=:buid'); + } + }else{ + if($bu_id==0){ + $db_st = $db->prepare('INSERT INTO BULIST ( BUName, Dir_Src, Dir_Dest, Files_Ex, BuType ) values( :bun, :bus, :bud, :buex, 0 )'); + }else{ + $db_st = $db->prepare('update BULIST set BUName=:bun, Dir_Src=:bus, Dir_Dest=:bud, Files_Ex=:buex where BUID=:buid'); + } + } + + if($bu_id>0){ + $db_st->bindValue(':buid', $bu_id ); + } + + $db_st->bindValue(':bun', $postVars['new_bu_name'] ); + $db_st->bindValue(':bus', $postVars['new_bu_source'] ); + $db_st->bindValue(':bud', $postVars['new_bu_dest'] ); + if( !$postVars['new_bu_is_dbType'] ){ + $db_st->bindValue(':buex', $postVars['new_bu_exf'] ); + } + + $db_st->execute(); + + if( $db->lastErrorCode()!==0){ + return $db->lastErrorCode(); + } + +} + + +?> diff --git a/html/inc/pagemap.php b/html/inc/pagemap.php new file mode 100644 index 0000000..11801de --- /dev/null +++ b/html/inc/pagemap.php @@ -0,0 +1,156 @@ +0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + } + + + if( $cmd=='new-item-bu' ){ + + include_once __DIR__ . "/new-file-bu.php"; + $db_err = save_bu_item( $postvars ); + if($db_err>0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + + } + + if( $cmd=='edit-item-bu' ){ + + include_once __DIR__ . "/new-file-bu.php"; + $db_err = save_bu_item( $postvars, $postvars['id'] ); + if($db_err>0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + + } + + if( $cmd=='delete-bu' ){ + + include_once __DIR__ . "/delete-file-bu.php"; + $db_err = delete_bu_item( $postvars['id'] ); + if($db_err>0){ + sendHtmlError( "DB error: $db_err" ); + return true; + } + + if($postvars['final-page'] == '' ){ + error_log( "Error: Expecting a final-page for: ". $cmd ); + sendHtmlError( "Error: Expecting a final-page for: $cmd in ". __FILE__ ); + return true; + } + return invokeCommand( $postvars['final-page'], $postvars ); + + } + +} + +?> diff --git a/html/inc/secure.php b/html/inc/secure.php new file mode 100644 index 0000000..0e1450a --- /dev/null +++ b/html/inc/secure.php @@ -0,0 +1,77 @@ +=4 ){ + //the user is logged in so nothing further to do in this script + return; +} + +//is there a login attempt +if( isset($_POST['username']) && isset($_POST['password']) ){ + + //any request must have the 'action' set to btn_login + if( !isset($_POST['action']) || $_POST['action']!='btn_login' ) { + error_log("Login Error: Missing or incorrect value for 'action'"); + exit; + } + + if( !isset($_POST['svr_nonce']) || $_POST['svr_nonce']=='' ) { + error_log("Login Error: Missing or blank value for 'svr_nonce'"); + exit; + } + + //has the correct nonce been returned? + if( $_POST['svr_nonce']!=$_SESSION['svr_nonce'] ){ + $_SESSION['svr_nonce'] = ''; // invalidate nonce + error_log("Login Error: Incorrect value for 'svr_nonce'"); + exit; + } + + include_once "common.php"; + include_once "login.php"; + if(authenticate( $_POST['username'], $_POST['password'], $_SESSION['svr_nonce'] ) ){ + + error_log("Login Success: ".$_POST['username']); + $_SESSION['username'] = $_POST['username']; + + //return the backup list page + include_once __DIR__ . "/bu_list_content.php"; + sendHtmlOk_WithData( [ 'authenticated', getbu_list_content() ] ); + + }else{ + error_log("Login Failure: ".$_POST['username']); + // failed authentication + // --- Send back the login page --- + $_SESSION['svr_nonce'] = generateNonce(); + sendHtmlOk_WithData( [ 'login', getLogin_content(), $_SESSION['svr_nonce'] ] ); + } + + exit; +} + +// no session is set +// no attempt to login was made +// likely that the page was just refreshed +// --- Send back the login page --- + +include_once "common.php"; +$_SESSION['svr_nonce'] = generateNonce(); + +include_once "login.php"; +sendHtmlOk_WithData( [ 'login', getLogin_content(), $_SESSION['svr_nonce'] ] ); +exit; //end the script to stop further processing by calling files + +//$svr_username = $_SESSION['username']; + +?> diff --git a/html/index.php b/html/index.php new file mode 100644 index 0000000..3232869 --- /dev/null +++ b/html/index.php @@ -0,0 +1,42 @@ + + + + + + Pearcey: Excecutive Backup Panel + + + + + + + + + +
+ +
+
Executive Backup Panel
+ Login +
+ +
+ +
+ +
Panel Help
+ +
+ +

For help using this panel, please email john@pearcey.net

+ +

This backup utility is open source and can be downloaded from pearcey.net

+ +

Please report bugs to john@pearcey.net

+
+ +
+
+ + + diff --git a/html/js/common.js b/html/js/common.js new file mode 100644 index 0000000..dc25eab --- /dev/null +++ b/html/js/common.js @@ -0,0 +1,101 @@ + +/** + * COPYRIGHT © 2022 JOHN PEARCEY + * All rights reserved +*/ + +/* + * pagename: + * The name of the server-side script to handle this post. + * fnCallback: + * The javascript callback function, called after the AJAX call completes. + * map_params: + * A map containing name value pairs required by the script. + * e.g. + * map_params = new Map(); + map_params.set('username', document.getElementById('username').value ); + map_params.set('pwdhash', document.getElementById('pwd').value ); + */ +function postData( pagename, fnCallback, map_params ){ + + //console.log( 'postData called' ); + + let formData = new FormData(); + map_params.forEach((value, key, map) => { + //if(key=='action') console.log("postData: ", pagename, value ); + formData.append( key, value ); + }); + + //https://www.digitalocean.com/community/tutorials/how-to-use-the-javascript-fetch-api-to-get-data + //https://jsonplaceholder.typicode.com/users + //https://javascript.info/fetch + fetch( pagename, { + method: 'POST', + body: formData, + + }) + /*.then((response) => { + return response.json(); */ + + .then(response => response.text()) + .then((dataStr) => { + + //console.log(dataStr); + + //there's a whole bunch of warnings and errors that might be sent by PHP. It is not possible + //to catch them in the serverside PHP code. So here we search for our json encoded return string which + //must be either Error or OK. + let pos = dataStr.indexOf("[\"OK\""); + let additional=null; + if(pos==-1){ + pos = dataStr.indexOf("[\"Error\""); + if(pos==-1){ + //the PHP is pretty fucked up if you get here! + console.warn( dataStr ); + let encodedStr = JSON.stringify( ["Illegal return from server. Did not find Error or OK.", dataStr ] ); + return JSON.parse( encodedStr ); + } + } + //console.log(dataStr); + return JSON.parse(dataStr); + + }).then((data) => { + fnCallback( true, data, map_params ); + + }).catch(function(error) { + console.log( error ); + fnCallback( false, error, map_params ); + }); + +} + +function checkReturn( success, data, bGetErr=false ){ + + if(!success){ + console.warn( "callback error: " + data ); + return false; + } + + if(data[0]=="Error"){ + if(bGetErr) return data; + alert( "Error: " + data[1] ); + for( i=2; i/gi, '\n'); + txt = txt.replace(/<\/?[^>]+>/gi, ''); //strip HTML + return txt; +} diff --git a/html/js/main.js b/html/js/main.js new file mode 100644 index 0000000..78fb441 --- /dev/null +++ b/html/js/main.js @@ -0,0 +1,454 @@ + +/** + * COPYRIGHT © 2022 JOHN PEARCEY + * All rights reserved +*/ + +/** + * The last nonce we just got from the server + */ +var g_svr_nonce = []; + +/** + * Every 10secs, we get the state of the backup list from the server. This is so that we can keep the front end up-to-date + * for the user. I'm sure that there are better ways with modern HTML5 but polling is easy and the application is not + * time critical. This is sufficient. + */ +var g_bu_list_state = []; + +/** + * This var g_stack_pages contains the app navigation pages in order. The top most page being the + * current page. So, to go back, you would remove the top most page and navigate to the new top page. +*/ +var g_stack_pages = []; + +function clear_page_state(){ + g_bu_list_state = []; + g_stack_pages = []; +} + +function push_page( pagename ){ + g_stack_pages.push( pagename ); +} + +/** + * Pops the current page off the stack and returns the penultimate one. + */ +function back_page( ){ + g_stack_pages.pop( ); + return g_stack_pages[g_stack_pages.length - 1]; +} + +function index_onload(){ + update_bu_state( false ); + set_main_content( true ); +} + +function update_bu_state( bCheckPage = true ){ + + setTimeout( () => { + update_bu_state(); + }, 10000); + + if(bCheckPage){ + //console.log( "update_bu_state pages:", g_stack_pages ); + if( g_stack_pages[g_stack_pages.length - 1] != 'backup-list' ) return; + } + + //console.log( "update_bu_state polling" ); + + map_params = new Map(); + map_params.set('action', 'backup-list-state' ); + + postData( "post_handler.php", function( success, data, m ){ + + if(data[1][0]=='login'){ + console.log( 'Cannot get backup-list-state: Not logged in' ); + return; + } + + let new_state = data[1][1]; + + //console.log( 'backup-list-state' + new_state ); + + if( g_bu_list_state.length == 0 ){ + //just store the new state + g_bu_list_state = new_state; + return; + } + + //compare the two arrays, if different length, then refresh the UI. + if( g_bu_list_state.length != new_state.length ){ + g_bu_list_state = new_state; + set_main_content( false ); + return; + } + + //check each row in turn + let bFound = false; + for( j=0; j"; + + //ensure the state map is also adjusted + for( j=0; j b.toString(16).padStart(2, "0")) + .join(""); // convert bytes to hex string + return hashHex; +} + +/** + * Validate the form for a new backup entry + * b_is_db_type is true for DB entry and false for file entry. + */ +function valid_btn_new_bu( b_is_db_type ){ + return true; +} + +/** + * Collect the form data together. + * b_is_db_type is true for DB entry and false for file entry. + */ +function set_btn_new_bu( b_is_db_type, map_params ){ + + map_params.set( 'new_bu_is_dbType', b_is_db_type?1:0 ); + map_params.set( 'new_bu_name', document.getElementById('new_bu_name').value ); + map_params.set( 'new_bu_source', document.getElementById('new_bu_source').value ); + map_params.set( 'new_bu_dest', document.getElementById('new_bu_dest').value ); + if(!b_is_db_type){ + map_params.set( 'new_bu_exf', document.getElementById('new_bu_exf').value ); + } +} + +/** + * Validate the form for the backup scheduling page + */ +function valid_btn_edit_shed_bu(){ + + sched_date = document.getElementById('sched_date').value; + if(sched_date=='') { + alert("Please choose a date"); + return false; + } + + sched_time = document.getElementById('sched_time').value; + if(sched_time=='') { + alert("Please choose a time"); + return false; + } + + let dt_chosen = new Date( sched_date + 'T' + sched_time + ':00Z'); + let now = new Date(); + + if(dt_chosen<=now){ + alert("Please choose a time in the future, not the past: ", dt_chosen, now ); + return false; + } + + console.log("dates: ", dt_chosen, now ); + + return true; +} + +/** + * Collect the form data together for the backup scheduling page + */ +function set_btn_edit_shed_bu( map_params ){ + + map_params.set( 'sched_date', document.getElementById('sched_date').value ); + map_params.set( 'sched_time', document.getElementById('sched_time').value ); + + switch(document.querySelector('input[name="sched_period"]:checked').id){ + case 'sched_ip_daily': + map_params.set( 'sched_period', 'D' ); + break; + + case 'sched_ip_weekly': + map_params.set( 'sched_period', 'W' ); + break; + + case 'sched_ip_monthly': + map_params.set( 'sched_period', 'M' ); + break; + + case 'sched_ip_none': + default: + map_params.set( 'sched_period', 'N' ); + } + + +} + diff --git a/html/phpinfo.php b/html/phpinfo.php new file mode 100644 index 0000000..cf60860 --- /dev/null +++ b/html/phpinfo.php @@ -0,0 +1,3 @@ + diff --git a/html/post_handler.php b/html/post_handler.php new file mode 100644 index 0000000..bce003d --- /dev/null +++ b/html/post_handler.php @@ -0,0 +1,25 @@ +