"""set_github_secrets.py Reads values from .env and publishes them as GitHub Actions secrets for a given repo. Usage: python set_github_secrets.py --owner --repo --keys KEY1 KEY2 ... Requires a GitHub token in the environment as `GITHUB_TOKEN` or `GH_TOKEN` with `repo` scope. """ import os import sys import json import argparse from pathlib import Path try: from dotenv import load_dotenv load_dotenv() except Exception: pass import requests import base64 try: from nacl import public, encoding except Exception: public = None def load_token(): for k in ("GITHUB_TOKEN", "GH_TOKEN"): v = os.getenv(k) if v: return v return None def get_public_key(token: str, owner: str, repo: str): url = f"https://api.github.com/repos/{owner}/{repo}/actions/secrets/public-key" headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github+json"} r = requests.get(url, headers=headers, timeout=15) if r.status_code != 200: raise RuntimeError(f"Failed to fetch public key: {r.status_code} {r.text}") return r.json() def encrypt_secret(public_key: str, value: str) -> str: if public is None: raise RuntimeError("PyNaCl is required to encrypt secrets. Install with 'pip install pynacl'.") pk = base64.b64decode(public_key) pk_obj = public.PublicKey(pk) sealed_box = public.SealedBox(pk_obj) encrypted = sealed_box.encrypt(value.encode('utf-8')) return base64.b64encode(encrypted).decode('utf-8') def put_secret(token: str, owner: str, repo: str, name: str, encrypted_value: str, key_id: str): url = f"https://api.github.com/repos/{owner}/{repo}/actions/secrets/{name}" headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github+json"} payload = {"encrypted_value": encrypted_value, "key_id": key_id} r = requests.put(url, headers=headers, data=json.dumps(payload), timeout=15) if r.status_code not in (201, 204): raise RuntimeError(f"Failed setting secret {name}: {r.status_code} {r.text}") return True DEFAULT_KEYS = [ 'FB_PAGE_ACCESS_TOKEN', 'FB_PAGE_ID', 'OPENAI_API_KEY', 'REPLICATE_API_TOKEN', 'DAILY_PROMPT', 'RUN_DAILY_REPLICATE', 'SHOW_POST_LOG', ] def main(): parser = argparse.ArgumentParser() parser.add_argument('--owner', required=True) parser.add_argument('--repo', required=True) parser.add_argument('--keys', nargs='*') args = parser.parse_args() token = load_token() if not token: print('No GitHub token found in env (GITHUB_TOKEN/GH_TOKEN). Aborting.') sys.exit(2) keys = args.keys or DEFAULT_KEYS to_publish = {} for k in keys: v = os.getenv(k) if v is not None and v != '': to_publish[k] = v if not to_publish: print('No configured keys found in environment to publish. Exiting.') return print(f'Publishing {len(to_publish)} secrets to {args.owner}/{args.repo}...') pk_info = get_public_key(token, args.owner, args.repo) key_id = pk_info.get('key_id') public_key = pk_info.get('key') if not key_id or not public_key: print('Failed to retrieve public key info from GitHub API.') sys.exit(3) for k, v in to_publish.items(): try: enc = encrypt_secret(public_key, v) put_secret(token, args.owner, args.repo, k, enc, key_id) print('OK', k) except Exception as e: print('ERROR', k, str(e)) if __name__ == '__main__': main()