#!/usr/bin/env python3 import os import sys import subprocess import hashlib import glob # Import helper functions sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin")) from helpers.functions import printfe, run_command def get_password(): """Get password from 1Password""" op_cmd = "op" # Try to get the password success, output = run_command( [op_cmd, "read", "op://j7nmhqlsjmp2r6umly5t75hzb4/Dotfiles Secrets/password"] ) if not success: printfe("red", "Failed to fetch password from 1Password.") return None # Check if we need to use a token if "use 'op item get" in output: # Extract the token token = output.split("use 'op item get ")[1].split(" --")[0] printfe("cyan", f"Got fetch token: {token}") # Use the token to get the actual password success, password = run_command( [op_cmd, "item", "get", token, "--reveal", "--fields", "password"] ) if not success: return None return password else: # We already got the password return output def prompt_for_password(): """Ask for password manually""" import getpass printfe("cyan", "Enter the password manually: ") password = getpass.getpass("") if not password: printfe("red", "Password cannot be empty.") sys.exit(1) printfe("green", "Password entered successfully.") return password def calculate_checksum(file_path): """Calculate SHA256 checksum of a file""" sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) return sha256_hash.hexdigest() def encrypt_folder(folder_path, password): """Recursively encrypt files in a folder""" for item in glob.glob(os.path.join(folder_path, "*")): # Skip .gpg and .sha256 files if item.endswith(".gpg") or item.endswith(".sha256"): continue # Handle directories recursively if os.path.isdir(item): encrypt_folder(item, password) continue # Calculate current checksum current_checksum = calculate_checksum(item) checksum_file = f"{item}.sha256" # Check if file changed since last encryption if os.path.exists(checksum_file): with open(checksum_file, "r") as f: previous_checksum = f.read().strip() if current_checksum == previous_checksum: continue # Remove existing .gpg file if it exists gpg_file = f"{item}.gpg" if os.path.exists(gpg_file): os.remove(gpg_file) # Encrypt the file printfe("cyan", f"Encrypting {item}...") cmd = [ "gpg", "--quiet", "--batch", "--yes", "--symmetric", "--cipher-algo", "AES256", "--armor", "--passphrase", password, "--output", gpg_file, item, ] success, _ = run_command(cmd) if success: printfe("cyan", f"Staging {item} for commit...") run_command(["git", "add", "-f", gpg_file]) # Update checksum file with open(checksum_file, "w") as f: f.write(current_checksum) else: printfe("red", f"Failed to encrypt {item}") def decrypt_folder(folder_path, password): """Recursively decrypt files in a folder""" for item in glob.glob(os.path.join(folder_path, "*")): # Handle .gpg files if item.endswith(".gpg"): output_file = item[:-4] # Remove .gpg extension printfe("cyan", f"Decrypting {item}...") cmd = [ "gpg", "--quiet", "--batch", "--yes", "--decrypt", "--passphrase", password, "--output", output_file, item, ] success, _ = run_command(cmd) if not success: printfe("red", f"Failed to decrypt {item}") # Process directories recursively elif os.path.isdir(item): printfe("cyan", f"Decrypting folder {item}...") decrypt_folder(item, password) def main(): if len(sys.argv) != 2 or sys.argv[1] not in ["encrypt", "decrypt"]: printfe("red", "Usage: secrets.py [encrypt|decrypt]") return 1 # Get the dotfiles path dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles")) secrets_path = os.path.join(dotfiles_path, "secrets") # Get the password password = get_password() if not password: password = prompt_for_password() # Perform the requested action if sys.argv[1] == "encrypt": printfe("cyan", "Encrypting secrets...") encrypt_folder(secrets_path, password) else: # decrypt printfe("cyan", "Decrypting secrets...") decrypt_folder(secrets_path, password) return 0 if __name__ == "__main__": sys.exit(main())