a digital person for bluesky
at main 12 kB view raw
1#!/usr/bin/env python3 2""" 3Configuration Migration Script for Void Bot 4Migrates from .env environment variables to config.yaml YAML configuration. 5""" 6 7import os 8import shutil 9from pathlib import Path 10import yaml 11from datetime import datetime 12 13 14def load_env_file(env_path=".env"): 15 """Load environment variables from .env file.""" 16 env_vars = {} 17 if not os.path.exists(env_path): 18 return env_vars 19 20 try: 21 with open(env_path, 'r', encoding='utf-8') as f: 22 for line_num, line in enumerate(f, 1): 23 line = line.strip() 24 # Skip empty lines and comments 25 if not line or line.startswith('#'): 26 continue 27 28 # Parse KEY=VALUE format 29 if '=' in line: 30 key, value = line.split('=', 1) 31 key = key.strip() 32 value = value.strip() 33 34 # Remove quotes if present 35 if value.startswith('"') and value.endswith('"'): 36 value = value[1:-1] 37 elif value.startswith("'") and value.endswith("'"): 38 value = value[1:-1] 39 40 env_vars[key] = value 41 else: 42 print(f"⚠️ Warning: Skipping malformed line {line_num} in .env: {line}") 43 except Exception as e: 44 print(f"❌ Error reading .env file: {e}") 45 46 return env_vars 47 48 49def create_config_from_env(env_vars, existing_config=None): 50 """Create YAML configuration from environment variables.""" 51 52 # Start with existing config if available, otherwise use defaults 53 if existing_config: 54 config = existing_config.copy() 55 else: 56 config = {} 57 58 # Ensure all sections exist 59 if 'letta' not in config: 60 config['letta'] = {} 61 if 'bluesky' not in config: 62 config['bluesky'] = {} 63 if 'bot' not in config: 64 config['bot'] = {} 65 66 # Map environment variables to config structure 67 env_mapping = { 68 'LETTA_API_KEY': ('letta', 'api_key'), 69 'BSKY_USERNAME': ('bluesky', 'username'), 70 'BSKY_PASSWORD': ('bluesky', 'password'), 71 'PDS_URI': ('bluesky', 'pds_uri'), 72 } 73 74 migrated_vars = [] 75 76 for env_var, (section, key) in env_mapping.items(): 77 if env_var in env_vars: 78 config[section][key] = env_vars[env_var] 79 migrated_vars.append(env_var) 80 81 # Set some sensible defaults if not already present 82 if 'timeout' not in config['letta']: 83 config['letta']['timeout'] = 600 84 85 if 'pds_uri' not in config['bluesky']: 86 config['bluesky']['pds_uri'] = "https://bsky.social" 87 88 # Add bot configuration defaults if not present 89 if 'fetch_notifications_delay' not in config['bot']: 90 config['bot']['fetch_notifications_delay'] = 30 91 if 'max_processed_notifications' not in config['bot']: 92 config['bot']['max_processed_notifications'] = 10000 93 if 'max_notification_pages' not in config['bot']: 94 config['bot']['max_notification_pages'] = 20 95 96 return config, migrated_vars 97 98 99def backup_existing_files(): 100 """Create backups of existing configuration files.""" 101 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 102 backups = [] 103 104 # Backup existing config.yaml if it exists 105 if os.path.exists("config.yaml"): 106 backup_path = f"config.yaml.backup_{timestamp}" 107 shutil.copy2("config.yaml", backup_path) 108 backups.append(("config.yaml", backup_path)) 109 110 # Backup .env if it exists 111 if os.path.exists(".env"): 112 backup_path = f".env.backup_{timestamp}" 113 shutil.copy2(".env", backup_path) 114 backups.append((".env", backup_path)) 115 116 return backups 117 118 119def load_existing_config(): 120 """Load existing config.yaml if it exists.""" 121 if not os.path.exists("config.yaml"): 122 return None 123 124 try: 125 with open("config.yaml", 'r', encoding='utf-8') as f: 126 return yaml.safe_load(f) or {} 127 except Exception as e: 128 print(f"⚠️ Warning: Could not read existing config.yaml: {e}") 129 return None 130 131 132def write_config_yaml(config): 133 """Write the configuration to config.yaml.""" 134 try: 135 with open("config.yaml", 'w', encoding='utf-8') as f: 136 # Write header comment 137 f.write("# Void Bot Configuration\n") 138 f.write("# Generated by migration script\n") 139 f.write(f"# Created: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") 140 f.write("# See config.yaml.example for all available options\n\n") 141 142 # Write YAML content 143 yaml.dump(config, f, default_flow_style=False, allow_unicode=True, indent=2) 144 145 return True 146 except Exception as e: 147 print(f"❌ Error writing config.yaml: {e}") 148 return False 149 150 151def main(): 152 """Main migration function.""" 153 print("🔄 Void Bot Configuration Migration Tool") 154 print("=" * 50) 155 print("This tool migrates from .env environment variables to config.yaml") 156 print() 157 158 # Check what files exist 159 has_env = os.path.exists(".env") 160 has_config = os.path.exists("config.yaml") 161 has_example = os.path.exists("config.yaml.example") 162 163 print("📋 Current configuration files:") 164 print(f" - .env file: {'✅ Found' if has_env else '❌ Not found'}") 165 print(f" - config.yaml: {'✅ Found' if has_config else '❌ Not found'}") 166 print(f" - config.yaml.example: {'✅ Found' if has_example else '❌ Not found'}") 167 print() 168 169 # If no .env file, suggest creating config from example 170 if not has_env: 171 if not has_config and has_example: 172 print("💡 No .env file found. Would you like to create config.yaml from the example?") 173 response = input("Create config.yaml from example? (y/n): ").lower().strip() 174 if response in ['y', 'yes']: 175 try: 176 shutil.copy2("config.yaml.example", "config.yaml") 177 print("✅ Created config.yaml from config.yaml.example") 178 print("📝 Please edit config.yaml to add your credentials") 179 return 180 except Exception as e: 181 print(f"❌ Error copying example file: {e}") 182 return 183 else: 184 print("👋 Migration cancelled") 185 return 186 else: 187 print("ℹ️ No .env file found and config.yaml already exists or no example available") 188 print(" If you need to set up configuration, see CONFIG.md") 189 return 190 191 # Load environment variables from .env 192 print("🔍 Reading .env file...") 193 env_vars = load_env_file() 194 195 if not env_vars: 196 print("⚠️ No environment variables found in .env file") 197 return 198 199 print(f" Found {len(env_vars)} environment variables") 200 for key in env_vars.keys(): 201 # Mask sensitive values 202 if 'KEY' in key or 'PASSWORD' in key: 203 value_display = f"***{env_vars[key][-4:]}" if len(env_vars[key]) > 4 else "***" 204 else: 205 value_display = env_vars[key] 206 print(f" - {key}={value_display}") 207 print() 208 209 # Load existing config if present 210 existing_config = load_existing_config() 211 if existing_config: 212 print("📄 Found existing config.yaml - will merge with .env values") 213 214 # Create configuration 215 print("🏗️ Building configuration...") 216 config, migrated_vars = create_config_from_env(env_vars, existing_config) 217 218 if not migrated_vars: 219 print("⚠️ No recognized configuration variables found in .env") 220 print(" Recognized variables: LETTA_API_KEY, BSKY_USERNAME, BSKY_PASSWORD, PDS_URI") 221 return 222 223 print(f" Migrating {len(migrated_vars)} variables: {', '.join(migrated_vars)}") 224 225 # Show preview 226 print("\n📋 Configuration preview:") 227 print("-" * 30) 228 229 # Show Letta section 230 if 'letta' in config and config['letta']: 231 print("🔧 Letta:") 232 for key, value in config['letta'].items(): 233 if 'key' in key.lower(): 234 display_value = f"***{value[-8:]}" if len(str(value)) > 8 else "***" 235 else: 236 display_value = value 237 print(f" {key}: {display_value}") 238 239 # Show Bluesky section 240 if 'bluesky' in config and config['bluesky']: 241 print("🐦 Bluesky:") 242 for key, value in config['bluesky'].items(): 243 if 'password' in key.lower(): 244 display_value = f"***{value[-4:]}" if len(str(value)) > 4 else "***" 245 else: 246 display_value = value 247 print(f" {key}: {display_value}") 248 249 print() 250 251 # Confirm migration 252 response = input("💾 Proceed with migration? This will update config.yaml (y/n): ").lower().strip() 253 if response not in ['y', 'yes']: 254 print("👋 Migration cancelled") 255 return 256 257 # Create backups 258 print("💾 Creating backups...") 259 backups = backup_existing_files() 260 for original, backup in backups: 261 print(f" Backed up {original}{backup}") 262 263 # Write new configuration 264 print("✍️ Writing config.yaml...") 265 if write_config_yaml(config): 266 print("✅ Successfully created config.yaml") 267 268 # Test the new configuration 269 print("\n🧪 Testing new configuration...") 270 try: 271 from config_loader import get_config 272 test_config = get_config() 273 print("✅ Configuration loads successfully") 274 275 # Test specific sections 276 try: 277 from config_loader import get_letta_config 278 letta_config = get_letta_config() 279 print("✅ Letta configuration valid") 280 except Exception as e: 281 print(f"⚠️ Letta config issue: {e}") 282 283 try: 284 from config_loader import get_bluesky_config 285 bluesky_config = get_bluesky_config() 286 print("✅ Bluesky configuration valid") 287 except Exception as e: 288 print(f"⚠️ Bluesky config issue: {e}") 289 290 except Exception as e: 291 print(f"❌ Configuration test failed: {e}") 292 return 293 294 # Success message and next steps 295 print("\n🎉 Migration completed successfully!") 296 print("\n📖 Next steps:") 297 print(" 1. Run: python test_config.py") 298 print(" 2. Test the bot: python bsky.py --test") 299 print(" 3. If everything works, you can optionally remove the .env file") 300 print(" 4. See CONFIG.md for more configuration options") 301 302 if backups: 303 print(f"\n🗂️ Backup files created:") 304 for original, backup in backups: 305 print(f" {backup}") 306 print(" These can be deleted once you verify everything works") 307 308 else: 309 print("❌ Failed to write config.yaml") 310 if backups: 311 print("🔄 Restoring backups...") 312 for original, backup in backups: 313 try: 314 if original != ".env": # Don't restore .env, keep it as fallback 315 shutil.move(backup, original) 316 print(f" Restored {backup}{original}") 317 except Exception as e: 318 print(f" ❌ Failed to restore {backup}: {e}") 319 320 321if __name__ == "__main__": 322 main()