Published on Nov. 19, 2025
Go homeManaging a vault with Age
Age is a simple, modern and secure file encryption tool, format, and Go library.
I have started using it for encrypting application secrets to be stored in git. This scripts assumes you are keeping your secrets within a directory called deployment.
deployment
├── build.py
├── Caddyfile
├── env.enc
├── recipients.txt
├── sites-app-server.service
├── sites-beat.service
└── sites-celery.service
The script will decrypt the secrets using your private key and present them in $EDITOR. If you change the secrets a backup is created. The new secrets will be encrypted using the public keys in recipients.txt.
#!/usr/bin/env python
import argparse
import datetime
import os
import pathlib
import shlex
import shutil
import subprocess
import sys
import tempfile
def get_resolved_absolute_path(path: str | pathlib.Path) -> pathlib.Path:
"""Helper function for getting a path."""
if isinstance(path, str):
path = pathlib.Path(path)
if path.is_absolute():
return path
return path.expanduser().resolve().absolute()
def get_identity_path() -> pathlib.Path | None:
"""Try get an identity file."""
home_path = pathlib.Path.home()
id_ed25519_path = home_path / ".ssh/id_ed25519"
if id_ed25519_path.exists():
return id_ed25519_path
id_rsa_path = home_path / ".ssh/id_rsa"
if id_rsa_path.exists():
return id_rsa_path
return None
if __name__ == "__main__":
parser = argparse.ArgumentParser("Print the contents of a vault.")
parser.add_argument("-i", "--identity", type=get_resolved_absolute_path, required=False)
args = parser.parse_args()
if not args.identity:
identity_path = get_identity_path()
else:
identity_path = args.identity
if not identity_path:
print("ERROR: Failed to find identity file. Try specifying one with --identity=<identity-file-path>")
sys.exit(1)
current_timestamp = int(datetime.datetime.now().timestamp())
base_path = pathlib.Path(__file__).parent.parent
deployment_path = base_path / "deployment"
recipients_path = deployment_path / "recipients.txt"
env_path = deployment_path / "env.enc"
env_backup_path = deployment_path / f"env.enc_{current_timestamp}.bak"
age_path_raw = shutil.which("age")
with tempfile.NamedTemporaryFile("wb") as stream:
decrypt_command_raw = f"{age_path_raw} -d -i {identity_path} {env_path}"
decrypt_command = shlex.split(decrypt_command_raw)
decrypt_process = subprocess.run(decrypt_command, text=False, check=True, capture_output=True)
env_content_pre_edit = decrypt_process.stdout
stream.write(env_content_pre_edit)
stream.flush()
editor_command_raw = os.path.expandvars(f"$EDITOR {stream.name}")
editor_command = shlex.split(editor_command_raw)
subprocess.run(editor_command, check=True, capture_output=False)
env_content_post_edit = pathlib.Path(stream.name).read_bytes()
if env_content_post_edit == env_content_pre_edit:
sys.exit(0)
env_backup_path.write_bytes(env_content_pre_edit)
print(f"Created backup {env_backup_path.name}")
encrypt_command_raw = f"{age_path_raw} -e -R {recipients_path} -o {env_path} {stream.name}"
encrypt_command = shlex.split(encrypt_command_raw)
encrypt_process = subprocess.run(encrypt_command, check=True, capture_output=True)
print(f"Wrote encrypted data to {env_path}")