#!/usr/bin/python3 # # import sys import os.path import argparse import configparser import getpass import pprint # For some verbose output try: import requests as _ except ImportError: sys.stderr.write("ERROR: requests package appears to be missing.\n" " Please use 'pip3 install requests'\n") sys.exit(1) try: import namecheap except ImportError: sys.stderr.write("ERROR: PyNamecheap package appears to be missing.\n" " Please use 'pip3 install PyNamecheap'\n") sys.exit(1) # Configuration file, in the user's home directory. CONFIG_FNAME = '.namecheap' # Bump this higher if the retries still don't get through DEFAULT_ATTEMPTS = 4 # Name of the TXT challenge record. ACME_CHALLENGE = '_acme-challenge' def main(args): cfg = configparser.ConfigParser() fname = os.path.join(os.path.expanduser('~'), CONFIG_FNAME) cfg.read(fname) ### just let the cfg[] fail, rather than fancy error domain = (args.domain or os.environ.get('CERTBOT_DOMAIN', cfg.get('certbot', 'domain', fallback=None))) ### fail ungracefully if validation isn't present validation = args.validation or os.environ['CERTBOT_VALIDATION'] assert domain and validation print('DOMAIN:', domain) print('VALIDATION:', validation) creds = cfg['credentials'] if args.prompt_key: api_key = getpass.getpass('API key: ') else: api_key = creds['api_key'] api = namecheap.Api(creds['username'], api_key, creds['username'], creds['ip_address'], sandbox=False, debug=args.api_debug, attempts_count=args.attempts) add_validation(api, domain, validation, args.verbose, args.dry_run) def add_validation(api, domain, validation, verbose=False, dry_run=False): hosts = api.domains_dns_getHosts(domain) print(f'INFO: domain "{domain}" has {len(hosts)} records') if verbose: print('HOSTS:') pprint.pprint(hosts) # Fix the record keys, since getHosts and setHosts uses different keys. # NOTE: this method is destructive, and alters the records we pass. for record in hosts: api._elements_names_fix(record) # Add or alter the challenge TXT record. for record in hosts: if record['HostName'] == ACME_CHALLENGE: record['Address'] = validation print('INFO: updated challenge record to:', validation) break else: hosts.append({ 'HostName': ACME_CHALLENGE, 'RecordType': 'TXT', 'Address': validation, }) print('INFO: added challenge record to:', validation) if verbose: print('NEW HOSTS:') pprint.pprint(hosts) if not dry_run: api.domains_dns_setHosts(domain, hosts) print(f'SETHOSTS: wrote {len(hosts)} records.') if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output of the script operation.') parser.add_argument('--api-debug', action='store_true', help='Verbose output for API exchanges with Namecheap.') parser.add_argument('--attempts', default=DEFAULT_ATTEMPTS, type=int, help='How many attempts for a successful API operations.') parser.add_argument('--domain', help='Domain for certificate generation,' ' overriding the CERTBOT_DOMAIN environment variable') parser.add_argument('--validation', help='Validation code for certificate generation,' ' overriding the CERTBOT_VALIDATION environment variable') parser.add_argument('--prompt-key', action='store_true', help='Prompt for the API key, rather than look on-disk.') parser.add_argument('-n', '--dry-run', action='store_true', help='Perform as much as possible, without changing data.') args = parser.parse_args() print('ARGS:', args) main(args)