dotfiles/bin/actions/secrets.py

160 lines
5.1 KiB
Python
Executable File

#!/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, "item", "get", "Dotfiles Secrets", "--fields", "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())