feat: complete monorepo structure with frontend and shared resources

- Add complete backend/ directory with full Django application
- Add frontend/ directory with Vite + TypeScript setup ready for Next.js
- Add comprehensive shared/ directory with:
  - Complete documentation and memory-bank archives
  - Media files and avatars (letters, park/ride images)
  - Deployment scripts and automation tools
  - Shared types and utilities
- Add architecture/ directory with migration guides
- Configure pnpm workspace for monorepo development
- Update .gitignore to exclude .django_tailwind_cli/ build artifacts
- Preserve all historical documentation in shared/docs/memory-bank/
- Set up proper structure for full-stack development with shared resources
This commit is contained in:
pacnpal
2025-08-23 18:40:07 -04:00
parent b0e0678590
commit d504d41de2
762 changed files with 142636 additions and 0 deletions

689
shared/scripts/vm/github-setup.py Executable file
View File

@@ -0,0 +1,689 @@
#!/usr/bin/env python3
"""
ThrillWiki GitHub PAT Setup Helper
Interactive script for setting up GitHub Personal Access Tokens with proper validation
and integration with the automation system.
Features:
- Guided GitHub PAT creation process
- Token validation and permission checking
- Integration with existing github-auth.py patterns
- Clear instructions for PAT scope requirements
- Secure token storage with proper file permissions
"""
import sys
import getpass
import requests
import argparse
import subprocess
from pathlib import Path
# Configuration
SCRIPT_DIR = Path(__file__).parent
PROJECT_DIR = SCRIPT_DIR.parent.parent
CONFIG_SCRIPT = SCRIPT_DIR / "automation-config.sh"
GITHUB_AUTH_SCRIPT = PROJECT_DIR / "scripts" / "github-auth.py"
TOKEN_FILE = PROJECT_DIR / ".github-pat"
# GitHub API Configuration
GITHUB_API_BASE = "https://api.github.com"
REQUEST_TIMEOUT = 30
# Token scope requirements for different use cases
TOKEN_SCOPES = {
"public": {
"description": "Public repositories only",
"scopes": ["public_repo"],
"note": "Suitable for public repositories and basic automation",
},
"private": {
"description": "Private repositories access",
"scopes": ["repo"],
"note": "Required for private repositories and full automation features",
},
"full": {
"description": "Full automation capabilities",
"scopes": ["repo", "workflow", "read:org"],
"note": "Recommended for complete automation setup with GitHub Actions",
},
}
class Colors:
"""ANSI color codes for terminal output"""
RED = "\033[0;31m"
GREEN = "\033[0;32m"
YELLOW = "\033[1;33m"
BLUE = "\033[0;34m"
PURPLE = "\033[0;35m"
CYAN = "\033[0;36m"
BOLD = "\033[1m"
NC = "\033[0m" # No Color
def print_colored(message, color=Colors.NC):
"""Print colored message to terminal"""
print(f"{color}{message}{Colors.NC}")
def print_error(message):
"""Print error message"""
print_colored(f"❌ Error: {message}", Colors.RED)
def print_success(message):
"""Print success message"""
print_colored(f"{message}", Colors.GREEN)
def print_warning(message):
"""Print warning message"""
print_colored(f"⚠️ Warning: {message}", Colors.YELLOW)
def print_info(message):
"""Print info message"""
print_colored(f" {message}", Colors.BLUE)
def print_step(step, total, message):
"""Print step progress"""
print_colored(f"\n[{step}/{total}] {message}", Colors.CYAN)
def validate_token_format(token):
"""Validate GitHub token format"""
if not token:
return False
# GitHub token patterns
patterns = [
lambda t: t.startswith("ghp_") and len(t) >= 40, # Classic PAT
lambda t: t.startswith("github_pat_") and len(t) >= 50, # Fine-grained PAT
lambda t: t.startswith("gho_") and len(t) >= 40, # OAuth token
lambda t: t.startswith("ghu_") and len(t) >= 40, # User token
lambda t: t.startswith("ghs_") and len(t) >= 40, # Server token
]
return any(pattern(token) for pattern in patterns)
def test_github_token(token, timeout=REQUEST_TIMEOUT):
"""Test GitHub token by making API call"""
if not token:
return False, "No token provided"
try:
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
response = requests.get(
f"{GITHUB_API_BASE}/user", headers=headers, timeout=timeout
)
if response.status_code == 200:
user_data = response.json()
return (
True,
f"Valid token for user: {
user_data.get(
'login', 'unknown')}",
)
elif response.status_code == 401:
return False, "Invalid or expired token"
elif response.status_code == 403:
return False, "Token lacks required permissions"
else:
return (
False,
f"API request failed with HTTP {
response.status_code}",
)
except requests.exceptions.RequestException as e:
return False, f"Network error: {str(e)}"
def get_token_permissions(token, timeout=REQUEST_TIMEOUT):
"""Get token permissions and scopes"""
if not token:
return None, "No token provided"
try:
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
# Get user info and check token in response headers
response = requests.get(
f"{GITHUB_API_BASE}/user", headers=headers, timeout=timeout
)
if response.status_code == 200:
scopes = response.headers.get("X-OAuth-Scopes", "").split(", ")
scopes = [scope.strip() for scope in scopes if scope.strip()]
return scopes, None
else:
return (
None,
f"Failed to get permissions: HTTP {
response.status_code}",
)
except requests.exceptions.RequestException as e:
return None, f"Network error: {str(e)}"
def check_repository_access(token, repo_url=None, timeout=REQUEST_TIMEOUT):
"""Check if token can access the repository"""
if not token:
return False, "No token provided"
# Try to determine repository from git remote
if not repo_url:
try:
result = subprocess.run(
["git", "remote", "get-url", "origin"],
cwd=PROJECT_DIR,
capture_output=True,
text=True,
timeout=10,
)
if result.returncode == 0:
repo_url = result.stdout.strip()
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
if not repo_url:
return None, "Could not determine repository URL"
# Extract owner/repo from URL
if "github.com" in repo_url:
# Handle both SSH and HTTPS URLs
if repo_url.startswith("git@github.com:"):
repo_path = repo_url.replace("git@github.com:", "").replace(".git", "")
elif "github.com/" in repo_url:
repo_path = repo_url.split("github.com/")[-1].replace(".git", "")
else:
return None, "Could not parse repository URL"
try:
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
response = requests.get(
f"{GITHUB_API_BASE}/repos/{repo_path}",
headers=headers,
timeout=timeout,
)
if response.status_code == 200:
repo_data = response.json()
return (
True,
f"Access confirmed for {
repo_data.get(
'full_name', repo_path)}",
)
elif response.status_code == 404:
return False, "Repository not found or no access"
elif response.status_code == 403:
return False, "Access denied - insufficient permissions"
else:
return (
False,
f"Access check failed: HTTP {
response.status_code}",
)
except requests.exceptions.RequestException as e:
return None, f"Network error: {str(e)}"
return None, "Not a GitHub repository"
def show_pat_instructions():
"""Show detailed PAT creation instructions"""
print_colored("\n" + "=" * 60, Colors.BOLD)
print_colored("GitHub Personal Access Token (PAT) Setup Guide", Colors.BOLD)
print_colored("=" * 60, Colors.BOLD)
print("\n🔐 Why do you need a GitHub PAT?")
print(" • Access private repositories")
print(" • Avoid GitHub API rate limits")
print(" • Enable automated repository operations")
print(" • Secure authentication without passwords")
print("\n📋 Step-by-step PAT creation:")
print(" 1. Go to: https://github.com/settings/tokens")
print(" 2. Click 'Generate new token''Generate new token (classic)'")
print(" 3. Enter a descriptive note: 'ThrillWiki Automation'")
print(" 4. Set expiration (recommended: 90 days for security)")
print(" 5. Select appropriate scopes:")
print("\n🎯 Recommended scope configurations:")
for scope_type, config in TOKEN_SCOPES.items():
print(f"\n {scope_type.upper()} REPOSITORIES:")
print(f" • Description: {config['description']}")
print(f" • Required scopes: {', '.join(config['scopes'])}")
print(f" • Note: {config['note']}")
print("\n⚡ Quick setup for most users:")
print(" • Select 'repo' scope for full repository access")
print(" • This enables all automation features")
print("\n🔒 Security best practices:")
print(" • Use descriptive token names")
print(" • Set reasonable expiration dates")
print(" • Regenerate tokens regularly")
print(" • Never share tokens in public")
print(" • Delete unused tokens immediately")
print("\n📱 After creating your token:")
print(" • Copy the token immediately (it won't be shown again)")
print(" • Return to this script and paste it when prompted")
print(" • The script will validate and securely store your token")
def interactive_token_setup():
"""Interactive token setup process"""
print_colored("\n🚀 ThrillWiki GitHub PAT Setup", Colors.BOLD)
print_colored("================================", Colors.BOLD)
# Check if token already exists
if TOKEN_FILE.exists():
try:
existing_token = TOKEN_FILE.read_text().strip()
if existing_token:
print_info("Existing GitHub token found")
# Test existing token
valid, message = test_github_token(existing_token)
if valid:
print_success(f"Current token is valid: {message}")
choice = (
input("\nDo you want to replace the existing token? (y/N): ")
.strip()
.lower()
)
if choice not in ["y", "yes"]:
print_info("Keeping existing token")
return True
else:
print_warning(f"Current token is invalid: {message}")
print_info("Setting up new token...")
except Exception as e:
print_warning(f"Could not read existing token: {e}")
# Show instructions
print("\n" + "=" * 50)
choice = (
input("Do you want to see PAT creation instructions? (Y/n): ").strip().lower()
)
if choice not in ["n", "no"]:
show_pat_instructions()
# Get token from user
print_step(1, 3, "Enter your GitHub Personal Access Token")
print("📋 Please paste your GitHub PAT below:")
print(" (Input will be hidden for security)")
while True:
try:
token = getpass.getpass("GitHub PAT: ").strip()
if not token:
print_error("No token entered. Please try again.")
continue
# Validate format
if not validate_token_format(token):
print_error(
"Invalid token format. GitHub tokens should start with 'ghp_', 'github_pat_', etc."
)
retry = input("Try again? (Y/n): ").strip().lower()
if retry in ["n", "no"]:
return False
continue
break
except KeyboardInterrupt:
print("\nSetup cancelled by user")
return False
# Test token
print_step(2, 3, "Validating GitHub token")
print("🔍 Testing token with GitHub API...")
valid, message = test_github_token(token)
if not valid:
print_error(f"Token validation failed: {message}")
return False
print_success(message)
# Check permissions
print("🔐 Checking token permissions...")
scopes, error = get_token_permissions(token)
if error:
print_warning(f"Could not check permissions: {error}")
else:
print_success(
f"Token scopes: {', '.join(scopes) if scopes else 'None detected'}"
)
# Check for recommended scopes
has_repo = "repo" in scopes or "public_repo" in scopes
if not has_repo:
print_warning("Token may lack repository access permissions")
# Check repository access
print("📁 Checking repository access...")
access, access_message = check_repository_access(token)
if access is True:
print_success(access_message)
elif access is False:
print_warning(access_message)
else:
print_info(access_message or "Repository access check skipped")
# Store token
print_step(3, 3, "Storing GitHub token securely")
try:
# Backup existing token if it exists
if TOKEN_FILE.exists():
backup_file = TOKEN_FILE.with_suffix(".backup")
TOKEN_FILE.rename(backup_file)
print_info(f"Existing token backed up to: {backup_file}")
# Write new token
TOKEN_FILE.write_text(token)
TOKEN_FILE.chmod(0o600) # Read/write for owner only
print_success(f"Token stored securely in: {TOKEN_FILE}")
# Try to update configuration via config script
try:
if CONFIG_SCRIPT.exists():
subprocess.run(
[
"bash",
"-c",
f'source {CONFIG_SCRIPT} && store_github_token "{token}"',
],
check=False,
capture_output=True,
)
print_success("Token added to automation configuration")
except Exception as e:
print_warning(f"Could not update automation config: {e}")
print_success("GitHub PAT setup completed successfully!")
return True
except Exception as e:
print_error(f"Failed to store token: {e}")
return False
def validate_existing_token():
"""Validate existing GitHub token"""
print_colored("\n🔍 GitHub Token Validation", Colors.BOLD)
print_colored("===========================", Colors.BOLD)
if not TOKEN_FILE.exists():
print_error("No GitHub token file found")
print_info(f"Expected location: {TOKEN_FILE}")
return False
try:
token = TOKEN_FILE.read_text().strip()
if not token:
print_error("Token file is empty")
return False
print_info("Validating stored token...")
# Format validation
if not validate_token_format(token):
print_error("Token format is invalid")
return False
print_success("Token format is valid")
# API validation
valid, message = test_github_token(token)
if not valid:
print_error(f"Token validation failed: {message}")
return False
print_success(message)
# Check permissions
scopes, error = get_token_permissions(token)
if error:
print_warning(f"Could not check permissions: {error}")
else:
print_success(
f"Token scopes: {
', '.join(scopes) if scopes else 'None detected'}"
)
# Check repository access
access, access_message = check_repository_access(token)
if access is True:
print_success(access_message)
elif access is False:
print_warning(access_message)
else:
print_info(access_message or "Repository access check inconclusive")
print_success("Token validation completed")
return True
except Exception as e:
print_error(f"Error reading token: {e}")
return False
def remove_token():
"""Remove stored GitHub token"""
print_colored("\n🗑️ GitHub Token Removal", Colors.BOLD)
print_colored("=========================", Colors.BOLD)
if not TOKEN_FILE.exists():
print_info("No GitHub token file found")
return True
try:
# Backup before removal
backup_file = TOKEN_FILE.with_suffix(".removed")
TOKEN_FILE.rename(backup_file)
print_success(f"Token removed and backed up to: {backup_file}")
# Try to remove from config
try:
if CONFIG_SCRIPT.exists():
subprocess.run(
[
"bash",
"-c",
f"source {CONFIG_SCRIPT} && remove_github_token",
],
check=False,
capture_output=True,
)
print_success("Token removed from automation configuration")
except Exception as e:
print_warning(f"Could not update automation config: {e}")
print_success("GitHub token removed successfully")
return True
except Exception as e:
print_error(f"Error removing token: {e}")
return False
def show_token_status():
"""Show current token status"""
print_colored("\n📊 GitHub Token Status", Colors.BOLD)
print_colored("======================", Colors.BOLD)
# Check token file
print(f"📁 Token file: {TOKEN_FILE}")
if TOKEN_FILE.exists():
print_success("Token file exists")
# Check permissions
perms = oct(TOKEN_FILE.stat().st_mode)[-3:]
if perms == "600":
print_success(f"File permissions: {perms} (secure)")
else:
print_warning(f"File permissions: {perms} (should be 600)")
# Quick validation
try:
token = TOKEN_FILE.read_text().strip()
if token:
if validate_token_format(token):
print_success("Token format is valid")
# Quick API test
valid, message = test_github_token(token, timeout=10)
if valid:
print_success(f"Token is valid: {message}")
else:
print_error(f"Token is invalid: {message}")
else:
print_error("Token format is invalid")
else:
print_error("Token file is empty")
except Exception as e:
print_error(f"Error reading token: {e}")
else:
print_warning("Token file not found")
# Check config integration
print(f"\n⚙️ Configuration: {CONFIG_SCRIPT}")
if CONFIG_SCRIPT.exists():
print_success("Configuration script available")
else:
print_warning("Configuration script not found")
# Check existing GitHub auth script
print(f"\n🔐 GitHub auth script: {GITHUB_AUTH_SCRIPT}")
if GITHUB_AUTH_SCRIPT.exists():
print_success("GitHub auth script available")
else:
print_warning("GitHub auth script not found")
def main():
"""Main CLI interface"""
parser = argparse.ArgumentParser(
description="ThrillWiki GitHub PAT Setup Helper",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s setup # Interactive token setup
%(prog)s validate # Validate existing token
%(prog)s status # Show token status
%(prog)s remove # Remove stored token
%(prog)s --help # Show this help
For detailed PAT creation instructions, run: %(prog)s setup
""",
)
parser.add_argument(
"command",
choices=["setup", "validate", "status", "remove", "help"],
help="Command to execute",
)
parser.add_argument(
"--token", help="GitHub token to validate (for validate command)"
)
parser.add_argument(
"--force", action="store_true", help="Force operation without prompts"
)
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
try:
if args.command == "setup":
success = interactive_token_setup()
sys.exit(0 if success else 1)
elif args.command == "validate":
if args.token:
# Validate provided token
print_info("Validating provided token...")
if validate_token_format(args.token):
valid, message = test_github_token(args.token)
if valid:
print_success(message)
sys.exit(0)
else:
print_error(message)
sys.exit(1)
else:
print_error("Invalid token format")
sys.exit(1)
else:
# Validate existing token
success = validate_existing_token()
sys.exit(0 if success else 1)
elif args.command == "status":
show_token_status()
sys.exit(0)
elif args.command == "remove":
if not args.force:
confirm = (
input("Are you sure you want to remove the GitHub token? (y/N): ")
.strip()
.lower()
)
if confirm not in ["y", "yes"]:
print_info("Operation cancelled")
sys.exit(0)
success = remove_token()
sys.exit(0 if success else 1)
elif args.command == "help":
parser.print_help()
sys.exit(0)
except KeyboardInterrupt:
print("\nOperation cancelled by user")
sys.exit(1)
except Exception as e:
print_error(f"Unexpected error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()