Major updates: LinkedIn auto-posting, timezone fixes, and Docker improvements
Features: - Add LinkedIn OAuth integration and auto-posting functionality - Add scheduler service for automated post publishing - Add metadata field to generated_posts for LinkedIn URLs - Add privacy policy page for LinkedIn API compliance - Add company management features and employee accounts - Add license key system for company registrations Fixes: - Fix timezone issues (use UTC consistently across app) - Fix datetime serialization errors in database operations - Fix scheduling timezone conversion (local time to UTC) - Fix import errors (get_database -> db) Infrastructure: - Update Docker setup to use port 8001 (avoid conflicts) - Add SSL support with nginx-proxy and Let's Encrypt - Add LinkedIn setup documentation - Add migration scripts for schema updates Services: - Add linkedin_service.py for LinkedIn API integration - Add scheduler_service.py for background job processing - Add storage_service.py for Supabase Storage - Add email_service.py improvements - Add encryption utilities for token storage Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
171
scripts/setup_linkedin_auth.py
Executable file
171
scripts/setup_linkedin_auth.py
Executable file
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to configure LinkedIn (OIDC) authentication for self-hosted Supabase.
|
||||
This script updates your .env file or docker-compose.yml with the necessary GoTrue environment variables.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ==================== CONFIGURATION ====================
|
||||
# TODO: Fill in your LinkedIn credentials below
|
||||
|
||||
LINKEDIN_CLIENT_ID = "your-linkedin-client-id"
|
||||
LINKEDIN_CLIENT_SECRET = "your-linkedin-client-secret"
|
||||
|
||||
# Your Supabase instance URL (where your self-hosted instance is accessible)
|
||||
SUPABASE_URL = "https://your-supabase-domain.com" # e.g., https://supabase.example.com
|
||||
|
||||
# Path to your Supabase docker-compose directory (where .env or docker-compose.yml is located)
|
||||
DOCKER_COMPOSE_DIR = "/path/to/your/supabase" # e.g., /home/user/supabase
|
||||
|
||||
# =======================================================
|
||||
|
||||
|
||||
def validate_config():
|
||||
"""Validate that all required configuration is set."""
|
||||
errors = []
|
||||
|
||||
if LINKEDIN_CLIENT_ID.startswith("your-"):
|
||||
errors.append("LINKEDIN_CLIENT_ID")
|
||||
if LINKEDIN_CLIENT_SECRET.startswith("your-"):
|
||||
errors.append("LINKEDIN_CLIENT_SECRET")
|
||||
if SUPABASE_URL.startswith("https://your-"):
|
||||
errors.append("SUPABASE_URL")
|
||||
if DOCKER_COMPOSE_DIR.startswith("/path/to/"):
|
||||
errors.append("DOCKER_COMPOSE_DIR")
|
||||
|
||||
if errors:
|
||||
print("❌ Error: Please update the following configuration variables in this script:")
|
||||
for var in errors:
|
||||
print(f" - {var}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def find_env_file():
|
||||
"""Find the .env file in the docker-compose directory."""
|
||||
compose_dir = Path(DOCKER_COMPOSE_DIR)
|
||||
|
||||
if not compose_dir.exists():
|
||||
print(f"❌ Error: Directory not found: {DOCKER_COMPOSE_DIR}")
|
||||
sys.exit(1)
|
||||
|
||||
# Look for common .env file locations
|
||||
env_files = [
|
||||
compose_dir / ".env",
|
||||
compose_dir / "docker" / ".env",
|
||||
compose_dir / ".env.local",
|
||||
]
|
||||
|
||||
for env_file in env_files:
|
||||
if env_file.exists():
|
||||
return env_file
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def update_env_file(env_file):
|
||||
"""Update the .env file with LinkedIn OAuth configuration."""
|
||||
|
||||
# LinkedIn OAuth configuration for GoTrue
|
||||
linkedin_config = f"""
|
||||
# LinkedIn OAuth Configuration (added by setup script)
|
||||
GOTRUE_EXTERNAL_LINKEDIN_ENABLED=true
|
||||
GOTRUE_EXTERNAL_LINKEDIN_CLIENT_ID={LINKEDIN_CLIENT_ID}
|
||||
GOTRUE_EXTERNAL_LINKEDIN_SECRET={LINKEDIN_CLIENT_SECRET}
|
||||
GOTRUE_EXTERNAL_LINKEDIN_REDIRECT_URI={SUPABASE_URL}/auth/v1/callback
|
||||
"""
|
||||
|
||||
# Read existing content
|
||||
if env_file:
|
||||
with open(env_file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check if LinkedIn config already exists
|
||||
if "GOTRUE_EXTERNAL_LINKEDIN_ENABLED" in content:
|
||||
print(f"⚠️ LinkedIn OAuth configuration already exists in {env_file}")
|
||||
print(" Please update it manually or remove the existing lines first.")
|
||||
return False
|
||||
|
||||
# Backup original file
|
||||
backup_file = env_file.with_suffix('.env.backup')
|
||||
with open(backup_file, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"📋 Backup created: {backup_file}")
|
||||
|
||||
# Append LinkedIn config
|
||||
with open(env_file, 'a') as f:
|
||||
f.write(linkedin_config)
|
||||
|
||||
print(f"✅ LinkedIn OAuth configuration added to {env_file}")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def create_env_snippet():
|
||||
"""Create a snippet file if no .env file is found."""
|
||||
snippet_file = Path(DOCKER_COMPOSE_DIR) / "linkedin_oauth_snippet.env"
|
||||
|
||||
linkedin_config = f"""# LinkedIn OAuth Configuration for GoTrue
|
||||
# Add these lines to your .env file or docker-compose.yml environment section
|
||||
|
||||
GOTRUE_EXTERNAL_LINKEDIN_ENABLED=true
|
||||
GOTRUE_EXTERNAL_LINKEDIN_CLIENT_ID={LINKEDIN_CLIENT_ID}
|
||||
GOTRUE_EXTERNAL_LINKEDIN_SECRET={LINKEDIN_CLIENT_SECRET}
|
||||
GOTRUE_EXTERNAL_LINKEDIN_REDIRECT_URI={SUPABASE_URL}/auth/v1/callback
|
||||
"""
|
||||
|
||||
with open(snippet_file, 'w') as f:
|
||||
f.write(linkedin_config)
|
||||
|
||||
print(f"📄 Created configuration snippet: {snippet_file}")
|
||||
print(" Copy these variables to your .env file or docker-compose.yml")
|
||||
return True
|
||||
|
||||
|
||||
def print_next_steps():
|
||||
"""Print instructions for completing the setup."""
|
||||
print("\n" + "="*60)
|
||||
print("✅ Configuration complete!")
|
||||
print("="*60)
|
||||
print("\nNext steps:\n")
|
||||
print("1. Restart your Supabase services:")
|
||||
print(f" cd {DOCKER_COMPOSE_DIR}")
|
||||
print(" docker-compose down")
|
||||
print(" docker-compose up -d")
|
||||
print()
|
||||
print("2. Add redirect URL in LinkedIn Developer Portal:")
|
||||
print(f" {SUPABASE_URL}/auth/v1/callback")
|
||||
print()
|
||||
print("3. Test the authentication in your application")
|
||||
print()
|
||||
print("4. Check GoTrue logs for any errors:")
|
||||
print(" docker-compose logs -f auth")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
print("🔧 Configuring LinkedIn OAuth for self-hosted Supabase")
|
||||
print("="*60)
|
||||
|
||||
validate_config()
|
||||
|
||||
env_file = find_env_file()
|
||||
|
||||
if env_file:
|
||||
print(f"📁 Found .env file: {env_file}")
|
||||
if update_env_file(env_file):
|
||||
print_next_steps()
|
||||
else:
|
||||
print("\n⚠️ Please update your configuration manually.")
|
||||
else:
|
||||
print("⚠️ No .env file found in the docker-compose directory")
|
||||
create_env_snippet()
|
||||
print_next_steps()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user