|
@@ -1,17 +1,25 @@
|
|
|
|
|
+#!/usr/bin/env python3
|
|
|
|
|
+
|
|
|
import os
|
|
import os
|
|
|
import subprocess
|
|
import subprocess
|
|
|
import toml
|
|
import toml
|
|
|
-from datetime import datetime, timedelta
|
|
|
|
|
-import shutil
|
|
|
|
|
|
|
+import argparse
|
|
|
|
|
+from datetime import datetime
|
|
|
|
|
+
|
|
|
|
|
+# Path to the configuration file
|
|
|
|
|
+CONFIG_PATH = "/etc/snap-slack/config.toml"
|
|
|
|
|
|
|
|
# Read configuration from a TOML file
|
|
# Read configuration from a TOML file
|
|
|
-def read_config(file_path):
|
|
|
|
|
|
|
+def read_config(file_path=CONFIG_PATH):
|
|
|
|
|
+ if not os.path.exists(file_path):
|
|
|
|
|
+ raise FileNotFoundError(f"Configuration file not found: {file_path}")
|
|
|
|
|
+
|
|
|
with open(file_path, 'r') as f:
|
|
with open(file_path, 'r') as f:
|
|
|
config = toml.load(f)
|
|
config = toml.load(f)
|
|
|
return config
|
|
return config
|
|
|
|
|
|
|
|
# Create a new snapshot with the current date and time
|
|
# Create a new snapshot with the current date and time
|
|
|
-def create_snapshot(config):
|
|
|
|
|
|
|
+def create(config):
|
|
|
date_str = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
date_str = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
|
snapshot_name = f"{config['snapshot']['snapshot_prefix']}{date_str}"
|
|
snapshot_name = f"{config['snapshot']['snapshot_prefix']}{date_str}"
|
|
|
snapshot_path = os.path.join(config['snapshot']['snapshot_dir'], snapshot_name)
|
|
snapshot_path = os.path.join(config['snapshot']['snapshot_dir'], snapshot_name)
|
|
@@ -31,11 +39,16 @@ def create_snapshot(config):
|
|
|
|
|
|
|
|
# Add an entry for the snapshot in elilo.conf
|
|
# Add an entry for the snapshot in elilo.conf
|
|
|
def add_elilo_entry(config, snapshot):
|
|
def add_elilo_entry(config, snapshot):
|
|
|
|
|
+ # Get the extra boot options from the config, if any
|
|
|
|
|
+ extra_boot_options = config['elilo'].get('extra_boot_options', '')
|
|
|
|
|
+
|
|
|
|
|
+ # Construct the boot entry with optional extra boot options
|
|
|
entry = f"""
|
|
entry = f"""
|
|
|
image=vmlinuz
|
|
image=vmlinuz
|
|
|
label={snapshot}
|
|
label={snapshot}
|
|
|
root={config['elilo']['root_partition']}
|
|
root={config['elilo']['root_partition']}
|
|
|
- append="rootflags=subvol={config['snapshot']['snapshot_dir']}/{snapshot} ro"
|
|
|
|
|
|
|
+ append="rootflags=subvol={config['snapshot']['snapshot_dir']}/{snapshot} ro {extra_boot_options}"
|
|
|
|
|
+ read-only
|
|
|
initrd=initrd.gz
|
|
initrd=initrd.gz
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
@@ -84,24 +97,33 @@ def remove_old_snapshots(config):
|
|
|
remove_elilo_entry(config, snapshot)
|
|
remove_elilo_entry(config, snapshot)
|
|
|
print(f"Removed snapshot: {snapshot}")
|
|
print(f"Removed snapshot: {snapshot}")
|
|
|
|
|
|
|
|
-# Function to wrap slackpkg upgrade
|
|
|
|
|
-def slackpkg_upgrade(config):
|
|
|
|
|
- print("Preparing to run slackpkg upgrade...")
|
|
|
|
|
- create_snapshot(config)
|
|
|
|
|
- print("Running slackpkg upgrade...")
|
|
|
|
|
- subprocess.run(["/usr/sbin/slackpkg", "upgrade-all"])
|
|
|
|
|
|
|
+# Adopt a snapshot as the new root subvolume
|
|
|
|
|
+def adopt(config, snapshot):
|
|
|
|
|
+ current_root_path = os.path.join(config['snapshot']['btrfs_mount_point'], config['snapshot']['root_subvolume'])
|
|
|
|
|
+ snapshot_path = os.path.join(config['snapshot']['snapshot_dir'], snapshot)
|
|
|
|
|
|
|
|
-# Function to wrap sbopkg -i
|
|
|
|
|
-def sbopkg_install(config, package):
|
|
|
|
|
- print(f"Preparing to install package using sbopkg: {package}")
|
|
|
|
|
- create_snapshot(config)
|
|
|
|
|
- print(f"Running sbopkg -i {package}...")
|
|
|
|
|
- subprocess.run(["/usr/sbin/sbopkg", "-i", package])
|
|
|
|
|
|
|
+ if not os.path.exists(snapshot_path):
|
|
|
|
|
+ raise Exception(f"Snapshot '{snapshot}' not found.")
|
|
|
|
|
|
|
|
-def main():
|
|
|
|
|
- # Load configuration from config.toml
|
|
|
|
|
- config = read_config("config.toml")
|
|
|
|
|
|
|
+ # Step 1: Rename the current root subvolume to something else (e.g., @old_root)
|
|
|
|
|
+ old_root_path = current_root_path + "_old"
|
|
|
|
|
+ print(f"Renaming current root subvolume to {old_root_path}")
|
|
|
|
|
+ cmd_rename_root = ["btrfs", "subvolume", "snapshot", current_root_path, old_root_path]
|
|
|
|
|
+ subprocess.run(cmd_rename_root, check=True)
|
|
|
|
|
+
|
|
|
|
|
+ # Step 2: Rename the selected snapshot to become the new root subvolume
|
|
|
|
|
+ print(f"Renaming snapshot '{snapshot}' to become the new root subvolume.")
|
|
|
|
|
+ cmd_set_new_root = ["btrfs", "subvolume", "snapshot", snapshot_path, current_root_path]
|
|
|
|
|
+ subprocess.run(cmd_set_new_root, check=True)
|
|
|
|
|
+
|
|
|
|
|
+ # Step 3: Update the elilo bootloader configuration to point to the new root subvolume
|
|
|
|
|
+ print(f"Updating elilo.conf to use the new root subvolume '{snapshot}'.")
|
|
|
|
|
+ add_elilo_entry(config, snapshot)
|
|
|
|
|
|
|
|
|
|
+ print(f"Snapshot '{snapshot}' has been adopted as the new root subvolume.")
|
|
|
|
|
+
|
|
|
|
|
+# Main logic for managing snapshots
|
|
|
|
|
+def update(config):
|
|
|
# Step 1: Get list of current snapshots and add them to elilo.conf if needed
|
|
# Step 1: Get list of current snapshots and add them to elilo.conf if needed
|
|
|
snapshots = list_snapshots(config)
|
|
snapshots = list_snapshots(config)
|
|
|
for snapshot in snapshots:
|
|
for snapshot in snapshots:
|
|
@@ -110,5 +132,33 @@ def main():
|
|
|
# Step 2: Remove snapshots older than retention period
|
|
# Step 2: Remove snapshots older than retention period
|
|
|
remove_old_snapshots(config)
|
|
remove_old_snapshots(config)
|
|
|
|
|
|
|
|
|
|
+# Parse command-line arguments
|
|
|
|
|
+def parse_arguments():
|
|
|
|
|
+ parser = argparse.ArgumentParser(description='Manage BTRFS snapshots and elilo bootloader entries.')
|
|
|
|
|
+ parser.add_argument('action', choices=['create', 'update', 'adopt'],
|
|
|
|
|
+ help='Specify the action to perform: create, manage, or adopt.')
|
|
|
|
|
+ parser.add_argument('--snapshot', help='The snapshot to adopt as the new root subvolume.')
|
|
|
|
|
+ return parser.parse_args()
|
|
|
|
|
+
|
|
|
|
|
+def main():
|
|
|
|
|
+ # Load configuration from /etc/manage_snapshots/config.toml
|
|
|
|
|
+ config = read_config()
|
|
|
|
|
+
|
|
|
|
|
+ # Parse command-line arguments
|
|
|
|
|
+ args = parse_arguments()
|
|
|
|
|
+
|
|
|
|
|
+ # Execute based on action
|
|
|
|
|
+ if args.action == 'create':
|
|
|
|
|
+ create(config)
|
|
|
|
|
+ elif args.action == 'update':
|
|
|
|
|
+ update(config)
|
|
|
|
|
+ elif args.action == 'adopt':
|
|
|
|
|
+ if not args.snapshot:
|
|
|
|
|
+ print("Error: You must specify the snapshot to adopt using --snapshot.")
|
|
|
|
|
+ else:
|
|
|
|
|
+ adopt(config, args.snapshot)
|
|
|
|
|
+ else:
|
|
|
|
|
+ print("Invalid action")
|
|
|
|
|
+
|
|
|
if __name__ == "__main__":
|
|
if __name__ == "__main__":
|
|
|
main()
|
|
main()
|