Spaces:
Sleeping
Sleeping
hf-actions
commited on
Commit
·
475fc0c
1
Parent(s):
b3ab169
chore: add set_github_secrets.py helper and PyNaCl requirement
Browse files- requirements.txt +2 -1
- set_github_secrets.py +119 -0
requirements.txt
CHANGED
|
@@ -3,4 +3,5 @@ requests>=2.0.0
|
|
| 3 |
openai>=0.27.0
|
| 4 |
gradio
|
| 5 |
replicate>=0.10.0
|
| 6 |
-
huggingface-hub>=0.18.0
|
|
|
|
|
|
| 3 |
openai>=0.27.0
|
| 4 |
gradio
|
| 5 |
replicate>=0.10.0
|
| 6 |
+
huggingface-hub>=0.18.0
|
| 7 |
+
pynacl>=1.5.0
|
set_github_secrets.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""set_github_secrets.py
|
| 2 |
+
|
| 3 |
+
Reads values from .env and publishes them as GitHub Actions secrets for a given repo.
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
python set_github_secrets.py --owner <owner> --repo <repo> --keys KEY1 KEY2 ...
|
| 7 |
+
|
| 8 |
+
Requires a GitHub token in the environment as `GITHUB_TOKEN` or `GH_TOKEN` with `repo` scope.
|
| 9 |
+
"""
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
import json
|
| 13 |
+
import argparse
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
from dotenv import load_dotenv
|
| 18 |
+
load_dotenv()
|
| 19 |
+
except Exception:
|
| 20 |
+
pass
|
| 21 |
+
|
| 22 |
+
import requests
|
| 23 |
+
import base64
|
| 24 |
+
try:
|
| 25 |
+
from nacl import public, encoding
|
| 26 |
+
except Exception:
|
| 27 |
+
public = None
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def load_token():
|
| 31 |
+
for k in ("GITHUB_TOKEN", "GH_TOKEN"):
|
| 32 |
+
v = os.getenv(k)
|
| 33 |
+
if v:
|
| 34 |
+
return v
|
| 35 |
+
return None
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def get_public_key(token: str, owner: str, repo: str):
|
| 39 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/actions/secrets/public-key"
|
| 40 |
+
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github+json"}
|
| 41 |
+
r = requests.get(url, headers=headers, timeout=15)
|
| 42 |
+
if r.status_code != 200:
|
| 43 |
+
raise RuntimeError(f"Failed to fetch public key: {r.status_code} {r.text}")
|
| 44 |
+
return r.json()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def encrypt_secret(public_key: str, value: str) -> str:
|
| 48 |
+
if public is None:
|
| 49 |
+
raise RuntimeError("PyNaCl is required to encrypt secrets. Install with 'pip install pynacl'.")
|
| 50 |
+
pk = base64.b64decode(public_key)
|
| 51 |
+
pk_obj = public.PublicKey(pk)
|
| 52 |
+
sealed_box = public.SealedBox(pk_obj)
|
| 53 |
+
encrypted = sealed_box.encrypt(value.encode('utf-8'))
|
| 54 |
+
return base64.b64encode(encrypted).decode('utf-8')
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def put_secret(token: str, owner: str, repo: str, name: str, encrypted_value: str, key_id: str):
|
| 58 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/actions/secrets/{name}"
|
| 59 |
+
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github+json"}
|
| 60 |
+
payload = {"encrypted_value": encrypted_value, "key_id": key_id}
|
| 61 |
+
r = requests.put(url, headers=headers, data=json.dumps(payload), timeout=15)
|
| 62 |
+
if r.status_code not in (201, 204):
|
| 63 |
+
raise RuntimeError(f"Failed setting secret {name}: {r.status_code} {r.text}")
|
| 64 |
+
return True
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
DEFAULT_KEYS = [
|
| 68 |
+
'FB_PAGE_ACCESS_TOKEN',
|
| 69 |
+
'FB_PAGE_ID',
|
| 70 |
+
'OPENAI_API_KEY',
|
| 71 |
+
'REPLICATE_API_TOKEN',
|
| 72 |
+
'DAILY_PROMPT',
|
| 73 |
+
'RUN_DAILY_REPLICATE',
|
| 74 |
+
'SHOW_POST_LOG',
|
| 75 |
+
]
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def main():
|
| 79 |
+
parser = argparse.ArgumentParser()
|
| 80 |
+
parser.add_argument('--owner', required=True)
|
| 81 |
+
parser.add_argument('--repo', required=True)
|
| 82 |
+
parser.add_argument('--keys', nargs='*')
|
| 83 |
+
args = parser.parse_args()
|
| 84 |
+
|
| 85 |
+
token = load_token()
|
| 86 |
+
if not token:
|
| 87 |
+
print('No GitHub token found in env (GITHUB_TOKEN/GH_TOKEN). Aborting.')
|
| 88 |
+
sys.exit(2)
|
| 89 |
+
|
| 90 |
+
keys = args.keys or DEFAULT_KEYS
|
| 91 |
+
to_publish = {}
|
| 92 |
+
for k in keys:
|
| 93 |
+
v = os.getenv(k)
|
| 94 |
+
if v is not None and v != '':
|
| 95 |
+
to_publish[k] = v
|
| 96 |
+
|
| 97 |
+
if not to_publish:
|
| 98 |
+
print('No configured keys found in environment to publish. Exiting.')
|
| 99 |
+
return
|
| 100 |
+
|
| 101 |
+
print(f'Publishing {len(to_publish)} secrets to {args.owner}/{args.repo}...')
|
| 102 |
+
pk_info = get_public_key(token, args.owner, args.repo)
|
| 103 |
+
key_id = pk_info.get('key_id')
|
| 104 |
+
public_key = pk_info.get('key')
|
| 105 |
+
if not key_id or not public_key:
|
| 106 |
+
print('Failed to retrieve public key info from GitHub API.')
|
| 107 |
+
sys.exit(3)
|
| 108 |
+
|
| 109 |
+
for k, v in to_publish.items():
|
| 110 |
+
try:
|
| 111 |
+
enc = encrypt_secret(public_key, v)
|
| 112 |
+
put_secret(token, args.owner, args.repo, k, enc, key_id)
|
| 113 |
+
print('OK', k)
|
| 114 |
+
except Exception as e:
|
| 115 |
+
print('ERROR', k, str(e))
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
if __name__ == '__main__':
|
| 119 |
+
main()
|