#!/usr/bin/env python3 import json import os import subprocess import sys import getpass BW_EMAIL = "jeanmi.tremblay@gmail.com" BW_PASS_PATH = "vaultwarden/jeanmi.tremblay@gmail.com/login" BW_TOTP_PATH = "vaultwarden/jeanmi.tremblay@gmail.com/authenticator" def run(cmd, input=None, check=True): result = subprocess.run(cmd, input=input, capture_output=True, text=True) if check and result.returncode != 0: print(result.stderr.strip(), file=sys.stderr) sys.exit(1) return result.stdout.strip() import tempfile SESSION_FILE = os.path.join(tempfile.gettempdir(), f"bw_session_{os.getuid()}") def save_session(session): with open(SESSION_FILE, "w") as f: f.write(session) os.chmod(SESSION_FILE, 0o600) def load_session(): try: with open(SESSION_FILE) as f: return f.read().strip() except FileNotFoundError: return "" def ensure_session(): session = load_session() or os.environ.get("BW_SESSION", "") if session: result = subprocess.run( ["bw", "unlock", "--check"], capture_output=True, env={**os.environ, "BW_SESSION": session} ) if result.returncode == 0: os.environ["BW_SESSION"] = session return session print("Touch your YubiKey to decrypt password...") password = run(["pass", "show", BW_PASS_PATH]) print("Touch your YubiKey to decrypt TOTP secret...") totp_secret = run(["pass", "show", BW_TOTP_PATH]) totp = run(["oathtool", "--totp", "-b", totp_secret]) print("Authenticating...") env = {**os.environ, "BW_PASSWORD": password} session = subprocess.run( ["bw", "login", BW_EMAIL, "--passwordenv", "BW_PASSWORD", "--code", totp, "--raw"], capture_output=True, text=True, env=env ).stdout.strip() if not session: session = subprocess.run( ["bw", "unlock", "--passwordenv", "BW_PASSWORD", "--raw"], capture_output=True, text=True, env=env ).stdout.strip() if not session: print("Failed to get session.", file=sys.stderr) sys.exit(1) save_session(session) os.environ["BW_SESSION"] = session return session def get_id(key, session): items = json.loads(run(["bw", "list", "items", "--search", key, "--session", session])) matches = [i for i in items if i["name"] == key] return matches[0]["id"] if matches else None def cmd_insert(key, session): val = getpass.getpass("Secret: ") val2 = getpass.getpass("Secret (again): ") if val != val2: print("Secrets do not match.", file=sys.stderr) sys.exit(1) item_id = get_id(key, session) if item_id: item = json.loads(run(["bw", "get", "item", item_id, "--session", session])) item["login"]["password"] = val encoded = run(["bw", "encode"], input=json.dumps(item)) run(["bw", "edit", "item", item_id, "--session", session], input=encoded) else: item = {"type": 1, "name": key, "login": {"username": "", "password": val}} encoded = run(["bw", "encode"], input=json.dumps(item)) run(["bw", "create", "item", "--session", session], input=encoded) print("Saved.") def cmd_generate(key, session): val = run(["bw", "generate", "-ulns", "--length", "32"]) item = {"type": 1, "name": key, "login": {"username": "", "password": val}} encoded = run(["bw", "encode"], input=json.dumps(item)) run(["bw", "create", "item", "--session", session], input=encoded) print(val) def cmd_show(key, session): result = subprocess.run( ["bw", "get", "password", key, "--session", session], capture_output=True, text=True ) if result.returncode != 0: print(f"Not found: {key}", file=sys.stderr) sys.exit(1) print(result.stdout.strip()) def print_help(): print("Usage: bww.py insert|generate|show \n") print("Examples:") print(" bww.py insert foo") print(" Prompt for a secret (twice). Creates or updates the Vaultwarden item named 'foo' with the provided password.") print("") print(" bww.py show bar") print(" Retrieves and prints the password for item 'bar' to stdout. Exits with error if not found.") print("") print(" bww.py generate foobar") print(" Generates a 32-character password, stores it as the password for item 'foobar', and prints the generated value.") print("") print("Notes:") print(" - The script reuses an unlocked Bitwarden session stored in a temp file or the BW_SESSION environment variable.") print(" - If no valid session exists the script will attempt to unlock/login using values from your local 'pass' store and an oathtool TOTP code (it may prompt you to touch your YubiKey).") print(" - Secrets and generated passwords are written to Vaultwarden via the 'bw' CLI.") def main(): # Support a simple help flag if len(sys.argv) >= 2 and sys.argv[1] in ("-h", "--help"): print_help() sys.exit(0) if len(sys.argv) < 3: print("Usage: bww insert|generate|show ", file=sys.stderr) sys.exit(1) cmd = sys.argv[1] key = sys.argv[2] session = ensure_session() if cmd == "insert": cmd_insert(key, session) elif cmd == "generate": cmd_generate(key, session) elif cmd == "show": cmd_show(key, session) else: print(f"Unknown command: {cmd}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()