2525CMS_USERNAME = os .environ .get ('CMS_USERNAME' , 'admin' )
2626CMS_PASSWORD_HASH = os .environ .get ('CMS_PASSWORD_HASH' , '' ) # bcrypt hash
2727CMS_PASSWORD = os .environ .get ('CMS_PASSWORD' , '' ) # Plain password (legacy, will hash it)
28- BACKUP_DIR = os .path . join ( os . path . dirname ( CMS_BASE_DIR ) , '.way-cms-backups' )
28+ BACKUP_DIR = os .environ . get ( 'BACKUP_DIR' , '/ .way-cms-backups' ) # Fixed path for docker mount
2929ALLOWED_EXTENSIONS = {'html' , 'htm' , 'css' , 'js' , 'txt' , 'xml' , 'json' , 'md' , 'png' , 'jpg' , 'jpeg' , 'gif' , 'svg' , 'webp' , 'zip' , 'ico' , 'woff' , 'woff2' , 'ttf' , 'eot' }
3030MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB max file size
3131READ_ONLY_MODE = os .environ .get ('READ_ONLY_MODE' , 'false' ).lower () == 'true'
@@ -1304,36 +1304,60 @@ def create_manual_backup():
13041304@app .route ('/api/folder-backups' , methods = ['GET' ])
13051305@login_required
13061306def list_folder_backups ():
1307- """List all folder backups (ZIP files)."""
1307+ """List all folder backups (ZIP files) including automatic backups ."""
13081308 folder_path = request .args .get ('path' , '' )
1309+
1310+ backups = []
1311+
1312+ # Check manual backups directory
13091313 folder_backup_dir = os .path .join (BACKUP_DIR , 'folders' , folder_path if folder_path else 'root' )
1314+ if os .path .exists (folder_backup_dir ):
1315+ try :
1316+ for item in os .listdir (folder_backup_dir ):
1317+ if item .endswith ('.zip' ):
1318+ item_path = os .path .join (folder_backup_dir , item )
1319+ if os .path .isfile (item_path ):
1320+ stat = os .stat (item_path )
1321+ backup_name = item [:- 4 ] if item .endswith ('.zip' ) else item
1322+
1323+ backups .append ({
1324+ 'name' : backup_name ,
1325+ 'filename' : item ,
1326+ 'path' : item_path .replace (BACKUP_DIR , '' ).lstrip ('/' ),
1327+ 'size' : stat .st_size ,
1328+ 'modified' : datetime .fromtimestamp (stat .st_mtime ).isoformat (),
1329+ 'formatted_date' : datetime .fromtimestamp (stat .st_mtime ).strftime ('%Y-%m-%d %H:%M:%S' ),
1330+ 'type' : 'manual'
1331+ })
1332+ except Exception as e :
1333+ print (f"Error reading manual backups: { e } " )
13101334
1311- if not os .path .exists (folder_backup_dir ):
1312- return jsonify ({'backups' : []})
1335+ # Check automatic backups directory (only for root path)
1336+ if not folder_path :
1337+ auto_backup_dir = os .path .join (BACKUP_DIR , 'auto' )
1338+ if os .path .exists (auto_backup_dir ):
1339+ try :
1340+ for item in os .listdir (auto_backup_dir ):
1341+ if item .endswith ('.zip' ):
1342+ item_path = os .path .join (auto_backup_dir , item )
1343+ if os .path .isfile (item_path ):
1344+ stat = os .stat (item_path )
1345+ backup_name = item [:- 4 ] if item .endswith ('.zip' ) else item
1346+
1347+ backups .append ({
1348+ 'name' : f"[Auto] { backup_name } " ,
1349+ 'filename' : item ,
1350+ 'path' : f"auto/{ item } " ,
1351+ 'size' : stat .st_size ,
1352+ 'modified' : datetime .fromtimestamp (stat .st_mtime ).isoformat (),
1353+ 'formatted_date' : datetime .fromtimestamp (stat .st_mtime ).strftime ('%Y-%m-%d %H:%M:%S' ),
1354+ 'type' : 'auto'
1355+ })
1356+ except Exception as e :
1357+ print (f"Error reading automatic backups: { e } " )
13131358
1314- backups = []
1315- try :
1316- for item in os .listdir (folder_backup_dir ):
1317- if item .endswith ('.zip' ):
1318- item_path = os .path .join (folder_backup_dir , item )
1319- if os .path .isfile (item_path ):
1320- stat = os .stat (item_path )
1321- # Extract backup name (without .zip)
1322- backup_name = item [:- 4 ] if item .endswith ('.zip' ) else item
1323-
1324- backups .append ({
1325- 'name' : backup_name ,
1326- 'filename' : item ,
1327- 'path' : item_path .replace (BACKUP_DIR , '' ).lstrip ('/' ),
1328- 'size' : stat .st_size ,
1329- 'modified' : datetime .fromtimestamp (stat .st_mtime ).isoformat (),
1330- 'formatted_date' : datetime .fromtimestamp (stat .st_mtime ).strftime ('%Y-%m-%d %H:%M:%S' )
1331- })
1332-
1333- backups .sort (key = lambda x : x ['modified' ], reverse = True )
1334- return jsonify ({'backups' : backups })
1335- except Exception as e :
1336- return jsonify ({'error' : str (e )}), 500
1359+ backups .sort (key = lambda x : x ['modified' ], reverse = True )
1360+ return jsonify ({'backups' : backups })
13371361
13381362
13391363@app .route ('/api/create-folder-backup' , methods = ['POST' ])
@@ -1435,6 +1459,29 @@ def restore_folder_backup():
14351459 return jsonify ({'error' : str (e )}), 500
14361460
14371461
1462+ @app .route ('/api/trigger-auto-backup' , methods = ['POST' ])
1463+ @login_required
1464+ @read_only_check
1465+ def trigger_auto_backup ():
1466+ """Manually trigger an automatic backup."""
1467+ try :
1468+ backup_path = create_automatic_backup ()
1469+ if backup_path :
1470+ stat = os .stat (backup_path )
1471+ return jsonify ({
1472+ 'success' : True ,
1473+ 'backup' : {
1474+ 'path' : backup_path .replace (BACKUP_DIR , '' ).lstrip ('/' ),
1475+ 'size' : stat .st_size ,
1476+ 'formatted_date' : datetime .fromtimestamp (stat .st_mtime ).strftime ('%Y-%m-%d %H:%M:%S' )
1477+ }
1478+ })
1479+ else :
1480+ return jsonify ({'error' : 'Failed to create backup' }), 500
1481+ except Exception as e :
1482+ return jsonify ({'error' : str (e )}), 500
1483+
1484+
14381485@app .route ('/api/delete-folder-backup' , methods = ['DELETE' ])
14391486@login_required
14401487@read_only_check
@@ -1771,20 +1818,24 @@ def get_website_name_for_backup():
17711818def create_automatic_backup ():
17721819 """Create an automatic backup of the entire website directory."""
17731820 if not AUTO_BACKUP_ENABLED :
1821+ print ("[AutoBackup] Automatic backups disabled" )
17741822 return None
17751823
17761824 try :
17771825 import zipfile
1778- import io
1826+
1827+ print (f"[AutoBackup] Creating backup... BACKUP_DIR={ BACKUP_DIR } , AUTO_BACKUP_DIR={ AUTO_BACKUP_DIR } " )
17791828
17801829 # Create auto backup directory
17811830 os .makedirs (AUTO_BACKUP_DIR , exist_ok = True )
1831+ print (f"[AutoBackup] Directory created/exists: { AUTO_BACKUP_DIR } " )
17821832
17831833 # Generate backup filename with timestamp
17841834 website_name = get_website_name_for_backup ()
17851835 timestamp = datetime .now ().strftime ('%Y%m%d_%H%M%S' )
17861836 backup_filename = f"{ website_name } _{ timestamp } .zip"
17871837 backup_path = os .path .join (AUTO_BACKUP_DIR , backup_filename )
1838+ print (f"[AutoBackup] Creating backup at: { backup_path } " )
17881839
17891840 # Create ZIP backup
17901841 with zipfile .ZipFile (backup_path , 'w' , zipfile .ZIP_DEFLATED ) as zip_file :
@@ -1801,11 +1852,14 @@ def create_automatic_backup():
18011852 zip_file .write (file_path , arcname )
18021853 except (OSError , IOError ) as e :
18031854 # Skip files that can't be read (permissions, etc.)
1804- print (f"Warning: Could not backup { file_path } : { e } " )
1855+ print (f"[AutoBackup] Warning: Could not backup { file_path } : { e } " )
18051856
1857+ print (f"[AutoBackup] Backup created successfully: { backup_path } " )
18061858 return backup_path
18071859 except Exception as e :
1808- print (f"Error creating automatic backup: { e } " )
1860+ print (f"[AutoBackup] Error creating automatic backup: { e } " )
1861+ import traceback
1862+ traceback .print_exc ()
18091863 return None
18101864
18111865def manage_backup_retention ():
0 commit comments