music on atproto
plyr.fm
1#!/usr/bin/env -S uv run --script --quiet
2"""Copy R2 bucket data from old buckets to new buckets."""
3
4from pathlib import Path
5
6import boto3
7from pydantic import Field
8from pydantic_settings import BaseSettings, SettingsConfigDict
9
10BASE_DIR = Path(__file__).resolve().parents[1]
11
12
13class R2Settings(BaseSettings):
14 """R2 credentials from environment."""
15
16 model_config = SettingsConfigDict(
17 env_file=str(BASE_DIR / ".env"),
18 env_file_encoding="utf-8",
19 extra="ignore",
20 case_sensitive=False,
21 )
22
23 aws_access_key_id: str = Field(validation_alias="AWS_ACCESS_KEY_ID")
24 aws_secret_access_key: str = Field(validation_alias="AWS_SECRET_ACCESS_KEY")
25 r2_endpoint_url: str = Field(validation_alias="R2_ENDPOINT_URL")
26
27
28def get_s3_client():
29 """Create S3 client for R2."""
30 settings = R2Settings()
31 return boto3.client(
32 "s3",
33 endpoint_url=settings.r2_endpoint_url,
34 aws_access_key_id=settings.aws_access_key_id,
35 aws_secret_access_key=settings.aws_secret_access_key,
36 region_name="auto",
37 )
38
39
40def copy_bucket(s3_client, source_bucket: str, dest_bucket: str):
41 """Copy all objects from source bucket to destination bucket."""
42 print(f"\n{'=' * 60}")
43 print(f"Copying from '{source_bucket}' to '{dest_bucket}'")
44 print(f"{'=' * 60}\n")
45
46 # List all objects in source bucket
47 paginator = s3_client.get_paginator("list_objects_v2")
48 pages = paginator.paginate(Bucket=source_bucket)
49
50 total_objects = 0
51 copied_objects = 0
52
53 for page in pages:
54 if "Contents" not in page:
55 print(f"No objects found in {source_bucket}")
56 return
57
58 for obj in page["Contents"]:
59 key = obj["Key"]
60 total_objects += 1
61
62 try:
63 # Copy object
64 copy_source = {"Bucket": source_bucket, "Key": key}
65 s3_client.copy_object(
66 CopySource=copy_source, Bucket=dest_bucket, Key=key
67 )
68 copied_objects += 1
69 print(f"✓ Copied: {key} ({obj['Size']} bytes)")
70 except Exception as e:
71 print(f"✗ Failed to copy {key}: {e}")
72
73 print(f"\n{'=' * 60}")
74 print(f"Summary: {copied_objects}/{total_objects} objects copied successfully")
75 print(f"{'=' * 60}\n")
76
77
78def main():
79 """Copy data from old buckets to new buckets."""
80 s3_client = get_s3_client()
81
82 # Define bucket mappings
83 bucket_mappings = [
84 ("relay", "audio-prod"),
85 ("relay-stg", "audio-staging"),
86 ]
87
88 print("\n🚀 Starting R2 bucket copy operation\n")
89
90 for source, dest in bucket_mappings:
91 try:
92 copy_bucket(s3_client, source, dest)
93 except Exception as e:
94 print(f"✗ Error copying {source} -> {dest}: {e}")
95 continue
96
97 print("✅ All copy operations completed!\n")
98
99
100if __name__ == "__main__":
101 main()