make.py (4129B)
1 #!/usr/bin/env -S uv run 2 from make3 import EchoAll, file_hash, make_main, once, shell 3 import asyncio 4 import json 5 import os 6 7 8 async def main(): 9 STORAGENAME = "blog-grace-moe" 10 STORAGEPASSWORD = ( 11 await shell("secret-tool lookup owner blog-grace-moe.b-cdn.net") 12 ).utf8stdout 13 14 PULLZONEID = "3580820" 15 APIPASSWORD = (await shell("secret-tool lookup owner grace@bunny.net")).utf8stdout 16 LOCALPATH = "public" 17 18 STORAGEURL = f"https://sg.storage.bunnycdn.com/{STORAGENAME}" 19 STORAGECMD = f"curl -H 'AccessKey: {STORAGEPASSWORD}' -s" 20 PURGEURL = f"https://api.bunny.net/pullzone/{PULLZONEID}/purgeCache" 21 APICMD = f"curl -H 'AccessKey: {APIPASSWORD}' -s" 22 23 @once() 24 async def rebuild(): 25 await shell( 26 """ 27 rm -rf public 28 rm -rf layouts/*.raw.shtml 29 for layout in layouts/*.shtml; do 30 tail -n+2 $layout > $layout.raw 31 done 32 rename .shtml.raw .raw.shtml layouts/*.shtml.raw 33 zine release 34 """, 35 echo=EchoAll, 36 ) 37 38 bunny_sem = asyncio.Semaphore(80) 39 40 @once() 41 async def contents(path: str): 42 async with bunny_sem: 43 print("+++ download", path) 44 path_json = await shell(f"{STORAGECMD} '{STORAGEURL}/{path}/'") 45 print("--- download", path) 46 return json.loads(path_json.utf8stdout) 47 48 @once() 49 async def cleanfile(path: str): 50 if not os.path.isfile(f"{LOCALPATH}/{path}"): 51 async with bunny_sem: 52 print("+++ cleanfile", path) 53 await shell(f"{STORAGECMD} -XDELETE '{STORAGEURL}/{path}'") 54 print("--- cleanfile", path) 55 56 @once() 57 async def cleandir(path: str): 58 if not os.path.isdir(f"{LOCALPATH}/{path}"): 59 async with bunny_sem: 60 print("+++ cleandir", path) 61 await shell(f"{STORAGECMD} -XDELETE '{STORAGEURL}/{path}/'") 62 print("--- cleandir", path) 63 64 @once() 65 async def clean(path: str): 66 path_contents = await contents(path) 67 await asyncio.gather( 68 *( 69 ( 70 (cleandir(path + "/" + ent["ObjectName"])) 71 if ent["IsDirectory"] 72 else (cleanfile(path + "/" + ent["ObjectName"])) 73 ) 74 for ent in path_contents 75 if isinstance(ent, dict) 76 ) 77 ) 78 # print("- clean", path) 79 80 @once() 81 async def upload(path: str): 82 path_contents = await contents(path[: path.rfind("/")]) 83 84 bunny_checksum = None 85 if isinstance(path_contents, list): 86 try: 87 bunny_checksum = next( 88 ( 89 ent["Checksum"] 90 for ent in path_contents 91 if ent["ObjectName"] == path[path.rfind("/") + 1 :] 92 ) 93 ) 94 except StopIteration: 95 pass 96 97 our_checksum = (await file_hash(f"{LOCALPATH}/{path}")).upper() 98 99 if bunny_checksum != our_checksum: 100 async with bunny_sem: 101 print("+++ uploading", path) 102 await shell( 103 f"{STORAGECMD} -T'{LOCALPATH}/{path}' '{STORAGEURL}/{path}'" 104 ) 105 print("--- uploading", path) 106 # print("- upload", path) 107 108 @once() 109 async def purge(): 110 async with bunny_sem: 111 print("+++ purge") 112 await shell(f"{APICMD} -XPOST '{PURGEURL}'") 113 print("--- purge") 114 115 @once() 116 async def all(): 117 await rebuild() 118 UPLOAD = (await shell(f"cd '{LOCALPATH}' && find . -type f")).utf8stdout 119 CLEAN = (await shell(f"cd '{LOCALPATH}' && find . -type d")).utf8stdout 120 await asyncio.gather( 121 *((upload(path)) for path in UPLOAD.strip().split("\n")), 122 *((clean(path)) for path in CLEAN.strip().split("\n")), 123 ) 124 await purge() 125 126 _ = all 127 return await make_main(locals()) 128 129 130 if __name__ == "__main__": 131 exit(asyncio.run(main()))