203 lines
6.1 KiB
Python
Executable File
203 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Production-ready Gitea deployment script
|
|
|
|
Combines environment generation and deployment with:
|
|
- Configuration validation
|
|
- Rollback capability
|
|
- Dry-run mode
|
|
- Monitoring hooks
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import NoReturn
|
|
|
|
from rich.console import Console
|
|
from rich.logging import RichHandler
|
|
|
|
from gitea_deployer.config import ConfigurationError, DeploymentConfig
|
|
from gitea_deployer.orchestrator import DeploymentError, DeploymentOrchestrator
|
|
|
|
|
|
console = Console()
|
|
|
|
|
|
def setup_logging(log_level: str) -> None:
|
|
"""
|
|
Setup rich logging with colored output
|
|
|
|
Args:
|
|
log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
|
|
"""
|
|
logging.basicConfig(
|
|
level=log_level.upper(),
|
|
format="%(message)s",
|
|
datefmt="[%X]",
|
|
handlers=[RichHandler(console=console, rich_tracebacks=True, show_path=False)]
|
|
)
|
|
|
|
# Reduce noise from urllib3/requests
|
|
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
logging.getLogger("requests").setLevel(logging.WARNING)
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""
|
|
Parse CLI arguments
|
|
|
|
Returns:
|
|
argparse.Namespace with parsed arguments
|
|
"""
|
|
parser = argparse.ArgumentParser(
|
|
description="Deploy Gitea with automatic environment generation",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Normal deployment
|
|
./deploy.py
|
|
|
|
# Dry-run mode (preview only)
|
|
./deploy.py --dry-run
|
|
|
|
# With webhook notifications
|
|
./deploy.py --webhook-url https://hooks.slack.com/xxx
|
|
|
|
# Debug mode
|
|
./deploy.py --log-level DEBUG
|
|
|
|
# Custom retry count
|
|
./deploy.py --max-retries 5
|
|
|
|
Environment Variables:
|
|
CLOUDFLARE_API_TOKEN Cloudflare API token (required)
|
|
CLOUDFLARE_ZONE_ID Cloudflare zone ID (required)
|
|
DEPLOYMENT_WEBHOOK_URL Webhook URL for notifications (optional)
|
|
DEPLOYMENT_MAX_RETRIES Max retries for DNS conflicts (default: 3)
|
|
|
|
For more information, see the documentation at:
|
|
/infra/templates/gitea/README.md
|
|
"""
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Preview deployment without making changes"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--env-file",
|
|
type=Path,
|
|
default=Path(".env"),
|
|
help="Path to .env file (default: .env)"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--compose-file",
|
|
type=Path,
|
|
default=Path("docker-compose.yml"),
|
|
help="Path to docker-compose.yml (default: docker-compose.yml)"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--max-retries",
|
|
type=int,
|
|
default=3,
|
|
help="Max retries for DNS conflicts (default: 3)"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--webhook-url",
|
|
type=str,
|
|
help="Webhook URL for deployment notifications"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--log-level",
|
|
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
default="INFO",
|
|
help="Logging level (default: INFO)"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--no-verify-ssl",
|
|
action="store_true",
|
|
help="Skip SSL verification for health checks (not recommended for production)"
|
|
)
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def print_banner() -> None:
|
|
"""Print deployment banner"""
|
|
console.print("\n[bold cyan]╔══════════════════════════════════════════════╗[/bold cyan]")
|
|
console.print("[bold cyan]║[/bold cyan] [bold white]Gitea Production Deployment[/bold white] [bold cyan]║[/bold cyan]")
|
|
console.print("[bold cyan]╚══════════════════════════════════════════════╝[/bold cyan]\n")
|
|
|
|
|
|
def main() -> NoReturn:
|
|
"""
|
|
Main entry point
|
|
|
|
Exit codes:
|
|
0: Success
|
|
1: Deployment failure
|
|
130: User interrupt (Ctrl+C)
|
|
"""
|
|
args = parse_args()
|
|
setup_logging(args.log_level)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
print_banner()
|
|
|
|
try:
|
|
# Load configuration
|
|
logger.debug("Loading configuration...")
|
|
config = DeploymentConfig.from_env_and_args(args)
|
|
config.validate()
|
|
logger.debug("Configuration loaded successfully")
|
|
|
|
if config.dry_run:
|
|
console.print("[bold yellow]━━━ DRY-RUN MODE: No changes will be made ━━━[/bold yellow]\n")
|
|
|
|
# Create orchestrator and deploy
|
|
orchestrator = DeploymentOrchestrator(config)
|
|
orchestrator.deploy()
|
|
|
|
console.print("\n[bold green]╔══════════════════════════════════════════════╗[/bold green]")
|
|
console.print("[bold green]║[/bold green] [bold white]✓ Deployment Successful![/bold white] [bold green]║[/bold green]")
|
|
console.print("[bold green]╚══════════════════════════════════════════════╝[/bold green]\n")
|
|
|
|
sys.exit(0)
|
|
|
|
except ConfigurationError as e:
|
|
logger.error(f"Configuration error: {e}")
|
|
console.print(f"\n[bold red]✗ Configuration error: {e}[/bold red]\n")
|
|
console.print("[yellow]Please check your environment variables and configuration.[/yellow]")
|
|
console.print("[yellow]Required: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID[/yellow]\n")
|
|
sys.exit(1)
|
|
|
|
except DeploymentError as e:
|
|
logger.error(f"Deployment failed: {e}")
|
|
console.print(f"\n[bold red]✗ Deployment failed: {e}[/bold red]\n")
|
|
sys.exit(1)
|
|
|
|
except KeyboardInterrupt:
|
|
logger.warning("Deployment interrupted by user")
|
|
console.print("\n[bold yellow]✗ Deployment interrupted by user[/bold yellow]\n")
|
|
sys.exit(130)
|
|
|
|
except Exception as e:
|
|
logger.exception("Unexpected error")
|
|
console.print(f"\n[bold red]✗ Unexpected error: {e}[/bold red]\n")
|
|
console.print("[yellow]Please check the logs above for more details.[/yellow]\n")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|