diff --git a/backend/scripts/fix_storage_acl.py b/backend/scripts/fix_storage_acl.py new file mode 100644 index 0000000..c123e38 --- /dev/null +++ b/backend/scripts/fix_storage_acl.py @@ -0,0 +1,88 @@ +""" +Fixup: call make_public() on every .bsm binary blob under melodies/ in Firebase Storage. +Run this if blobs were uploaded but are returning AccessDenied. + + docker exec -it bellsystems-backend python scripts/fix_storage_acl.py --dry-run + docker exec -it bellsystems-backend python scripts/fix_storage_acl.py +""" + +import argparse +import os +import sys +from pathlib import Path + +# --------------------------------------------------------------------------- +# .env loader +# --------------------------------------------------------------------------- + +def _load_env() -> dict: + search = Path(__file__).resolve().parent + for _ in range(4): + env_file = search / ".env" + if env_file.exists(): + result = {} + for line in env_file.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, val = line.partition("=") + result[key.strip()] = val.strip().strip('"').strip("'") + print(f"[INFO] Loaded config from {env_file}") + return result + search = search.parent + return {} + +_env = _load_env() + +def _cfg(key: str, default: str = "") -> str: + return _env.get(key) or os.environ.get(key) or default + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def run(dry_run: bool = False): + label = "[DRY-RUN]" if dry_run else "[LIVE]" + + sa_path = _cfg("FIREBASE_SERVICE_ACCOUNT_PATH", "./firebase-service-account.json") + bucket_name = _cfg("FIREBASE_STORAGE_BUCKET") + + if not bucket_name: + print("ERROR: FIREBASE_STORAGE_BUCKET not set in .env") + sys.exit(1) + + import firebase_admin + from firebase_admin import credentials, storage as fb_storage + + cred = credentials.Certificate(sa_path) + firebase_admin.initialize_app(cred, {"storageBucket": bucket_name}) + bucket = fb_storage.bucket() + + print(f"\n{label} Scanning melodies/ in bucket: {bucket_name}\n") + + blobs = list(bucket.list_blobs(prefix="melodies/")) + bsm_blobs = [b for b in blobs if b.name.lower().endswith(".bsm")] + + print(f"Found {len(bsm_blobs)} .bsm blob(s)\n") + + fixed = 0 + for blob in bsm_blobs: + print(f" {'[skip] ' if dry_run else '[fix] '}{blob.name}") + if not dry_run: + try: + blob.make_public() + fixed += 1 + except Exception as e: + print(f" ERROR: {e}") + + print(f"\n{label} Done. {fixed if not dry_run else len(bsm_blobs)} blob(s) {'would be ' if dry_run else ''}made public.") + if dry_run: + print("Run without --dry-run to apply.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Make all melody .bsm blobs public in Firebase Storage") + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + run(dry_run=args.dry_run)