Added resume function and checkpoints for broken

setups
This commit is contained in:
n0mad1k
2025-10-21 20:50:23 -04:00
parent d1c0206393
commit a76b70c298
Regular → Executable
+272 -34
View File
@@ -25,6 +25,20 @@ KALI_ISO = ""
TAILS_ISO = ""
CUSTOM_ISO = ""
DRIVE = ""
STATE_FILE = "/tmp/covert_sd_state.json"
# Checkpoint states
CHECKPOINTS = {
"VERACRYPT_DOWNLOADED": False,
"DRIVE_SELECTED": False,
"DRIVE_WIPED": False,
"ISO_WRITTEN": False,
"PARTITIONS_CREATED": False,
"PERSISTENCE_SETUP": False,
"DOCS_ENCRYPTED": False,
"TOOLS_SETUP": False,
"COMPLETE": False
}
def log(message):
"""Logs a message to both the log file and the terminal."""
@@ -32,6 +46,75 @@ def log(message):
log_file.write(message + "\n")
print(message)
def save_state(checkpoint_name, data=None):
"""Save current progress to state file."""
global CHECKPOINTS
CHECKPOINTS[checkpoint_name] = True
state = {
"checkpoints": CHECKPOINTS,
"drive": DRIVE,
"timestamp": TIMESTAMP,
"log_file": LOG_FILE,
"create_kali": CREATE_KALI,
"create_docs": CREATE_DOCS,
"create_tails": CREATE_TAILS,
"create_custom": CREATE_CUSTOM,
"kali_iso": KALI_ISO,
"tails_iso": TAILS_ISO,
"custom_iso": CUSTOM_ISO,
"fast_mode": FAST_MODE,
"paranoid_mode": PARANOID_MODE
}
if data:
state.update(data)
try:
with open(STATE_FILE, 'w') as f:
json.dump(state, f, indent=2)
log(f"✓ Checkpoint saved: {checkpoint_name}")
except Exception as e:
log(f"⚠ Warning: Could not save checkpoint: {e}")
def load_state():
"""Load previous progress from state file."""
global CHECKPOINTS, DRIVE, TIMESTAMP, LOG_FILE
global CREATE_KALI, CREATE_DOCS, CREATE_TAILS, CREATE_CUSTOM
global KALI_ISO, TAILS_ISO, CUSTOM_ISO, FAST_MODE, PARANOID_MODE
if not os.path.exists(STATE_FILE):
return None
try:
with open(STATE_FILE, 'r') as f:
state = json.load(f)
CHECKPOINTS = state.get("checkpoints", CHECKPOINTS)
DRIVE = state.get("drive", "")
TIMESTAMP = state.get("timestamp", TIMESTAMP)
LOG_FILE = state.get("log_file", LOG_FILE)
CREATE_KALI = state.get("create_kali", False)
CREATE_DOCS = state.get("create_docs", False)
CREATE_TAILS = state.get("create_tails", False)
CREATE_CUSTOM = state.get("create_custom", False)
KALI_ISO = state.get("kali_iso", "")
TAILS_ISO = state.get("tails_iso", "")
CUSTOM_ISO = state.get("custom_iso", "")
FAST_MODE = state.get("fast_mode", False)
PARANOID_MODE = state.get("paranoid_mode", False)
return state
except Exception as e:
log(f"⚠ Warning: Could not load state: {e}")
return None
def clear_state():
"""Remove state file when setup is complete."""
if os.path.exists(STATE_FILE):
os.remove(STATE_FILE)
log("✓ State file cleared")
def run_command(command, shell=False, interactive=False, ignore_enospc=False):
"""
Runs a system command.
@@ -135,9 +218,51 @@ def download_portable_veracrypt():
log("This is a one-time download that will be stored on your device.")
log("")
# Use consistent temp directory for caching across runs
temp_dir = "/tmp/veracrypt_download"
# Check if we already have cached downloads
if os.path.exists(temp_dir):
log(f"✓ Found existing VeraCrypt cache at: {temp_dir}")
log("Checking if all required files are present...")
# Check if all files exist and are recent enough
cache_valid = True
cached_files = []
for platform, info in {
"Windows": "VeraCrypt-Portable-Windows.exe",
"Linux AppImage": "VeraCrypt-Portable-Linux.AppImage",
"Linux DEB": "veracrypt-Ubuntu-22.04-amd64.deb",
"macOS": "VeraCrypt-MacOS.dmg"
}.items():
file_path = os.path.join(temp_dir, info)
if os.path.exists(file_path):
size_mb = os.path.getsize(file_path) / (1024 * 1024)
cached_files.append(f"{platform}: {size_mb:.1f} MB")
else:
cache_valid = False
break
if cache_valid:
log("✓ All VeraCrypt files found in cache!")
for cf in cached_files:
log(cf)
log("")
use_cache = input("Use cached files? (y/n) [Default: y]: ") or "y"
if use_cache.lower() == "y":
log("✓ Using cached VeraCrypt downloads")
return temp_dir
else:
log("Removing old cache and downloading fresh copies...")
shutil.rmtree(temp_dir)
else:
log("⚠ Cache incomplete, will re-download")
shutil.rmtree(temp_dir)
# Create temp directory for downloads
temp_dir = tempfile.mkdtemp(prefix="veracrypt_download_")
log(f"Using temporary directory: {temp_dir}")
os.makedirs(temp_dir, exist_ok=True)
log(f"Downloading to: {temp_dir}")
log("")
# Get latest VeraCrypt version from GitHub
@@ -322,26 +447,69 @@ def setup_usb():
# Download portable VeraCrypt first (so it's ready to copy later)
veracrypt_dir = None
if CREATE_DOCS:
if CREATE_DOCS and not CHECKPOINTS["VERACRYPT_DOWNLOADED"]:
veracrypt_dir = download_portable_veracrypt()
save_state("VERACRYPT_DOWNLOADED", {"veracrypt_dir": veracrypt_dir})
elif CREATE_DOCS:
# Already downloaded, use cached version
veracrypt_dir = "/tmp/veracrypt_download"
log("✓ Skipping VeraCrypt download (already completed)")
log("\n" + "="*70)
log("STEP 1: DRIVE SELECTION")
log("="*70)
log("Available storage devices on your system:")
list_drives()
log("\nIMPORTANT: Double-check the drive path! All data will be erased.")
DRIVE = input("Enter the drive to use for USB (e.g., /dev/sda) [Default: /dev/sda]: ") or "/dev/sda"
if not CHECKPOINTS["DRIVE_SELECTED"]:
log("\n" + "="*70)
log("STEP 1: DRIVE SELECTION")
log("="*70)
log("Available storage devices on your system:")
list_drives()
log("\n⚠️ WARNING: This will repartition and format the selected drive.")
log("Make sure you select the correct device!")
confirm = input(f"\nYou have selected {DRIVE}. Is this correct? (y/n) [Default: y]: ") or "y"
if confirm.lower() != "y":
log("Drive selection canceled. Exiting.")
sys.exit(1)
while True:
DRIVE = input("\nEnter the drive to use (e.g., /dev/sdb): ")
log("\n" + "="*70)
log("STEP 2: PREPARING DRIVE")
log("="*70)
prepare_drive(DRIVE)
if not DRIVE:
log("Error: No drive specified. Please enter a drive path.")
continue
# Check if drive exists
if not os.path.exists(DRIVE):
log(f"Error: Drive {DRIVE} does not exist!")
log("Please check the available drives listed above and try again.")
retry = input("Try again? (y/n) [Default: y]: ") or "y"
if retry.lower() != "y":
log("Drive selection canceled. Exiting.")
sys.exit(1)
continue
# Check if it's a block device
if not os.path.isdir(DRIVE) and not os.path.exists(DRIVE):
log(f"Error: {DRIVE} is not a valid block device!")
continue
# Confirm selection - more accurate warning
log(f"\nYou selected: {DRIVE}")
log("The script will:")
log(" • Clear partition table")
log(" • Create new partitions")
log(" • Optionally wipe (you'll be asked)")
log(" • Format with new filesystems")
confirm = input(f"\nProceed with {DRIVE}? (yes/no) [Default: no]: ")
if confirm.lower() == "yes":
break
else:
log("Drive selection canceled.")
retry = input("Select a different drive? (y/n) [Default: y]: ") or "y"
if retry.lower() != "y":
log("Exiting.")
sys.exit(1)
log("\n" + "="*70)
log("STEP 2: PREPARING DRIVE")
log("="*70)
prepare_drive(DRIVE)
save_state("DRIVE_SELECTED")
else:
log(f"✓ Skipping drive selection (already selected: {DRIVE})")
if CREATE_KALI or CREATE_TAILS or CREATE_DOCS:
log("\n" + "="*70)
@@ -477,19 +645,19 @@ def setup_usb():
if iso_written:
if CREATE_KALI or CREATE_CUSTOM:
# Both Kali and custom ISOs use the same partitioning approach
fix_partition_table()
fix_partition_table(veracrypt_dir)
elif CREATE_TAILS:
if CREATE_DOCS:
# For Tails with docs, add partitions after Tails
fix_partition_table_tails()
fix_partition_table_tails(veracrypt_dir)
else:
# Tails only, no additional partitions
log("Tails installation complete. No additional partitions requested.")
elif CREATE_DOCS:
# If no ISO was written, just set up documents partition
fix_partition_table_docs_only()
fix_partition_table_docs_only(veracrypt_dir)
def fix_partition_table_docs_only():
def fix_partition_table_docs_only(veracrypt_dir=None):
"""
Sets up partitions solely for encrypted documents without altering existing OS partitions.
"""
@@ -562,7 +730,7 @@ def fix_partition_table_docs_only():
setup_unencrypted_partition(veracrypt_dir)
setup_docs_partition()
def fix_partition_table_tails():
def fix_partition_table_tails(veracrypt_dir=None):
"""
Adds encrypted documents partition after Tails installation without breaking boot.
Tails creates its own partition table, so we extend it carefully.
@@ -655,7 +823,7 @@ def fix_partition_table_tails():
log("Tails + encrypted documents partition setup complete!")
def fix_partition_table():
def fix_partition_table(veracrypt_dir=None):
"""
Fixes the partition table for Kali Linux by adding persistence and documents partitions.
"""
@@ -888,7 +1056,7 @@ def setup_docs_partition():
log("\nStarting VeraCrypt encryption (this may take a few minutes)...")
log("="*70)
log("VERACRYPT WILL ASK YOU THESE QUESTIONS:")
log("VERACRYPT PROMPTS - FOLLOW THESE STEPS:")
log("="*70)
log("")
log("1. 'Enter password:' → Type your strong password (20+ characters)")
@@ -897,18 +1065,16 @@ def setup_docs_partition():
log("2. 'Re-enter password:' → Type the SAME password again")
log(" Then press ENTER")
log("")
log("3. 'Enter PIM:' → Just press ENTER")
log(f" (We're using PIM {pim_value} automatically)")
log("")
log("4. 'Enter keyfile path [none]:' → Just press ENTER")
log("3. 'Enter keyfile path [none]:' → Just press ENTER")
log(" (Keyfiles not needed for most users)")
log("")
log("5. 'Please type at least 320 randomly chosen characters'")
log(" → Smash random keys on your keyboard until it says 'Done'")
log(" This creates entropy for encryption")
log("4. 'Please type at least 320 randomly chosen characters'")
log(" → Smash random keys on your keyboard")
log(" → Keep typing until it shows 'Characters remaining: 0'")
log(" → Then press ENTER")
log(" (This creates random entropy for encryption)")
log("")
log("6. 'Protect hidden volume (if any)? (y=Yes/n=No) [No]:' → Type: n")
log(" Then press ENTER (we're not using hidden volumes)")
log(f"️ NOTE: PIM is set to {pim_value} automatically (not prompted)")
log("")
log("="*70)
log("Creating volume now...")
@@ -920,6 +1086,7 @@ def setup_docs_partition():
log(f" Hash: SHA-512")
log(f" PIM: {pim_value}")
log(f" Filesystem: ext4")
save_state("DOCS_ENCRYPTED")
def setup_unencrypted_partition(veracrypt_dir=None):
"""
@@ -1028,6 +1195,7 @@ def setup_unencrypted_partition(veracrypt_dir=None):
log("\nUnmounting tools partition...")
run_command("sudo umount /mnt/unencrypted", shell=True)
log("✓ Tools partition setup complete")
save_state("TOOLS_SETUP")
def get_last_partition_number():
"""
@@ -1081,12 +1249,68 @@ def main():
parser.add_argument("--fast", action="store_true", help="Enable fast setup with less secure encryption")
parser.add_argument("--paranoid", action="store_true", help="Enable paranoid mode: maximum security, 3-pass shred, highest encryption")
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
parser.add_argument("-r", "--resume", action="store_true", help="Resume from previous incomplete setup")
args = parser.parse_args()
# Prevent using -a with -t; argparse handles this due to mutually exclusive group
# However, if you want to provide a custom error message or additional handling, you can add it here
# Handle resume flag
if args.resume:
previous_state = load_state()
if previous_state:
log("\n" + "="*70)
log("🔄 RESUMING PREVIOUS SETUP")
log("="*70)
log("")
log("Found incomplete setup from previous run:")
log(f" Drive: {previous_state.get('drive', 'Unknown')}")
log(f" Started: {previous_state.get('timestamp', 'Unknown')}")
log("")
log("Completed steps:")
for checkpoint, completed in previous_state.get('checkpoints', {}).items():
status = "" if completed else ""
log(f" {status} {checkpoint}")
log("")
log("✓ Resuming from last checkpoint...")
log(f"Continuing log in: {LOG_FILE}")
log("")
# State already loaded by load_state()
# Skip to setup_usb() - it will check checkpoints
check_dependencies()
setup_usb()
return
else:
log("Error: No previous setup found to resume.")
log("State file not found at: /tmp/covert_sd_state.json")
log("")
log("To start a new setup, run without --resume flag:")
log(" sudo ./covert_sd_card_tool.py -d -a -t /path/to/tails.iso")
sys.exit(1)
# Check if state exists but user didn't use --resume flag
previous_state = load_state()
if previous_state:
log("\n" + "="*70)
log("⚠️ PREVIOUS INCOMPLETE SETUP FOUND")
log("="*70)
log("")
log(f"Drive: {previous_state.get('drive', 'Unknown')}")
log(f"Started: {previous_state.get('timestamp', 'Unknown')}")
log("")
log("You have two options:")
log(" 1. Resume previous setup: sudo ./covert_sd_card_tool.py --resume")
log(" 2. Start fresh (WARNING: will overwrite any existing setup)")
log("")
choice = input("Continue with fresh setup? (yes/no) [Default: no]: ") or "no"
if choice.lower() != "yes":
log("Exiting. Use --resume flag to continue previous setup.")
sys.exit(0)
else:
log("Starting fresh setup (old state will be cleared)...")
clear_state()
if not any([args.all, args.kali, args.docs, args.tails, args.custom]):
parser.print_help()
sys.exit(1)
@@ -1164,7 +1388,21 @@ def main():
log("")
log(f"Log file saved: {LOG_FILE}")
log("")
if CREATE_DOCS and os.path.exists("/tmp/veracrypt_download"):
cache_size = sum(os.path.getsize(os.path.join("/tmp/veracrypt_download", f))
for f in os.listdir("/tmp/veracrypt_download")
if os.path.isfile(os.path.join("/tmp/veracrypt_download", f))) / (1024 * 1024)
log("💡 TIP: VeraCrypt downloads cached at /tmp/veracrypt_download")
log(f" Size: {cache_size:.1f} MB")
log(" Next SD card creation will skip downloads!")
log(" To clean up: rm -rf /tmp/veracrypt_download")
log("")
log("="*70)
# Mark as complete and clear state file
save_state("COMPLETE")
clear_state()
if __name__ == "__main__":
main()