smart-deploy 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: smart-deploy
3
+ Version: 0.1.0
4
+ Requires-Dist: typer
5
+ Requires-Dist: fastapi
6
+ Requires-Dist: uvicorn
7
+ Requires-Dist: requests
8
+ Requires-Dist: pydantic
9
+ Dynamic: requires-dist
@@ -0,0 +1,103 @@
1
+ # 🚀 Smart Deploy
2
+
3
+ ![Python Version](https://img.shields.io/badge/python-3.8%2B-blue)
4
+ ![License](https://img.shields.io/badge/license-MIT-green)
5
+ ![FastAPI](https://img.shields.io/badge/FastAPI-0.100%2B-009688?logo=fastapi)
6
+ ![Typer](https://img.shields.io/badge/Typer-CLI-000000)
7
+
8
+ **Smart Deploy** is a lightweight, zero-configuration CI/CD tool designed for independent developers and small teams. It bridges the gap between your GitHub repositories and your Linux server, automating the entire deployment process with a single command.
9
+
10
+ No complex YAML files. No heavy Jenkins pipelines. Just raw efficiency.
11
+
12
+ ## ✨ Features
13
+
14
+ - **Zero-Config Webhooks**: Automatically configures GitHub webhooks using your Personal Access Token.
15
+ - **Smart Framework Detection**: Automatically detects Node.js (Next.js/React) and Python projects.
16
+ - **Auto Dependency Resolution**: Runs `npm install`, `npm run build`, or `pip install` based on your project's ecosystem.
17
+ - **Custom Post-Deploy Commands**: Seamlessly integrates with process managers like PM2 or Systemd.
18
+ - **Multi-Project Support**: Manage dozens of projects on a single server with one lightweight background listener.
19
+
20
+ ---
21
+
22
+ ## 📦 Installation
23
+
24
+ Since Smart Deploy is built with Python, you can install it directly on your server using `pip`:
25
+
26
+ ```bash
27
+ # Clone the repository
28
+ git clone [https://github.com/AmirtahaNemati/smart-deploy.git](https://github.com/AmirtahaNemati/smart-deploy.git)
29
+ cd smart-deploy
30
+
31
+ # Install the package globally
32
+ pip install -e .
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 🚀 Quick Start
38
+
39
+ ### 1. Start the Listener
40
+
41
+ Spin up the background FastAPI server that listens for GitHub push events.
42
+
43
+ ```bash
44
+ smart-deploy start --port 9000
45
+
46
+ ```
47
+
48
+ _(Tip: In a production environment, run this using `nohup` or create a `systemd` service to keep it alive in the background)._
49
+
50
+ ### 2. Register Your Project
51
+
52
+ Link your GitHub repository to a local directory on your server. Smart Deploy will automatically configure the GitHub webhook for you.
53
+
54
+ ```bash
55
+ smart-deploy add [https://github.com/YourUsername/your-repo](https://github.com/YourUsername/your-repo) /var/www/your-project http://YOUR_SERVER_IP:9000 --token "ghp_your_github_token" --restart "pm2 restart my-app"
56
+
57
+ ```
58
+
59
+ **Parameters Explained:**
60
+
61
+ - `https://.../your-repo`: The target GitHub repository.
62
+ - `/var/www/your-project`: The absolute path to your project on the server.
63
+ - `http://YOUR_SERVER_IP:9000`: Your server's public URL/IP and the port the listener is running on.
64
+ - `--token`: Your GitHub Personal Access Token (requires `repo` scope to set up webhooks).
65
+ - `--restart`: (Optional) The command to run after pulling and building the code.
66
+
67
+ ### 3. Push and Relax
68
+
69
+ Now, simply push your code from your local machine:
70
+
71
+ ```bash
72
+ git commit -m "feat: awesome new feature"
73
+ git push origin main
74
+
75
+ ```
76
+
77
+ Smart Deploy will intercept the webhook, pull the latest code, install dependencies, build the project, and restart the service automatically.
78
+
79
+ ---
80
+
81
+ ## 🧠 How the Smart Detector Works
82
+
83
+ When a webhook is triggered, Smart Deploy analyzes the target directory:
84
+
85
+ 1. **Next.js / Node.js**: If `package.json` and `next.config.js` are found, it executes `npm install` followed by `npm run build`.
86
+ 2. **Python**: If `requirements.txt` is found, it automatically locates your virtual environment (`venv`) and runs `pip install -r requirements.txt`.
87
+ 3. **Custom Execution**: Finally, it runs the custom restart command provided during the `add` step.
88
+
89
+ ---
90
+
91
+ ## 🛠️ CLI Commands Reference
92
+
93
+ - `smart-deploy add`: Register a new project and set up webhooks.
94
+ - `smart-deploy start`: Launch the FastAPI webhook receiver.
95
+ - `smart-deploy --help`: View the detailed help menu.
96
+
97
+ ---
98
+
99
+ ## 📝 License
100
+
101
+ This project is licensed under the MIT License - see the [LICENSE](https://www.google.com/search?q=LICENSE&utm_source=gemini) file for details.
102
+
103
+ Developed with ❤️ by [Amirtaha Nemati](https://www.google.com/search?q=https://github.com/AmirtahaNemati&utm_source=gemini).
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,19 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="smart-deploy",
5
+ version="0.1.0",
6
+ packages=find_packages(),
7
+ install_requires=[
8
+ "typer",
9
+ "fastapi",
10
+ "uvicorn",
11
+ "requests",
12
+ "pydantic"
13
+ ],
14
+ entry_points={
15
+ "console_scripts": [
16
+ "smart-deploy=smart_deploy.cli:app",
17
+ ],
18
+ },
19
+ )
File without changes
@@ -0,0 +1,83 @@
1
+ import typer
2
+ import shutil
3
+ import getpass
4
+ import subprocess
5
+ from .database import save_project
6
+ from .github_api import setup_webhook
7
+
8
+ app = typer.Typer(help="Smart Deploy: Automated CI/CD Tool for Developers")
9
+
10
+ @app.command()
11
+ def add(
12
+ repo_url: str = typer.Argument(..., help="Target GitHub Repository URL"),
13
+ path: str = typer.Argument(..., help="Absolute local path on the server"),
14
+ server_url: str = typer.Argument(..., help="Public URL or IP of this server (e.g., http://8.8.8.8:9000)"),
15
+ token: str = typer.Option(None, "--token", "-t", help="GitHub Personal Access Token (requires 'repo' permissions)"),
16
+ restart_cmd: str = typer.Option(None, "--restart", "-r", help="Custom command to run after deployment (e.g., 'pm2 restart api')")
17
+ ):
18
+ """
19
+ Register a new project and automatically configure its GitHub webhook.
20
+ """
21
+ typer.echo(f"Registering project: {repo_url} at {path}")
22
+
23
+ # ذخیره کانفیگ پروژه در سیستم جهت ارجاع هنگام دریافت درخواست وب‌هوک
24
+ repo_name = save_project(repo_url, path, token, restart_cmd)
25
+ typer.secho(f"Project '{repo_name}' successfully added to the local database.", fg=typer.colors.GREEN)
26
+
27
+ # در صورت وجود توکن، ارتباط با گیت‌هاب و ثبت وب‌هوک آغاز می‌شود
28
+ if token:
29
+ typer.echo("Initiating automated GitHub webhook configuration...")
30
+ if setup_webhook(repo_name, token, server_url):
31
+ typer.secho("GitHub Webhook configured successfully.", fg=typer.colors.GREEN)
32
+ else:
33
+ typer.secho("Failed to configure GitHub Webhook. Verify token permissions.", fg=typer.colors.RED)
34
+ else:
35
+ typer.secho("Warning: No token provided. Automated webhook configuration skipped.", fg=typer.colors.YELLOW)
36
+
37
+ @app.command()
38
+ def start(port: int = typer.Option(9000, "--port", "-p", help="Port number for the webhook listener")):
39
+ """
40
+ Start the background FastAPI server to listen for GitHub webhooks.
41
+ """
42
+ typer.secho(f"Initializing Smart Deploy Listener on port {port}...", fg=typer.colors.CYAN)
43
+
44
+ # فراخوانی Uvicorn به عنوان سرور اجرای FastAPI
45
+ subprocess.run(["uvicorn", "smart_deploy.server:app", "--host", "0.0.0.0", "--port", str(port)])
46
+
47
+ @app.command()
48
+ def generate_service(port: int = typer.Option(9000, "--port", "-p", help="Port number for the webhook listener")):
49
+ """
50
+ Generate a systemd service configuration for Linux servers to run in the background.
51
+ """
52
+ # پیدا کردن مسیر دقیق فایل اجرایی و نام کاربری لینوکس
53
+ executable = shutil.which("smart-deploy") or "/usr/local/bin/smart-deploy"
54
+ user = getpass.getuser()
55
+
56
+ service_content = f"""[Unit]
57
+ Description=Smart Deploy Webhook Listener
58
+ After=network.target
59
+
60
+ [Service]
61
+ User={user}
62
+ ExecStart={executable} start --port {port}
63
+ Restart=always
64
+ RestartSec=3
65
+
66
+ [Install]
67
+ WantedBy=multi-user.target
68
+ """
69
+
70
+ typer.secho("1. Create a new service file using nano:", fg=typer.colors.CYAN)
71
+ typer.echo("sudo nano /etc/systemd/system/smart-deploy.service\n")
72
+
73
+ typer.secho("2. Paste the following configuration into the file:\n", fg=typer.colors.CYAN)
74
+ typer.echo(service_content)
75
+
76
+ typer.secho("3. Enable and start the service by running:", fg=typer.colors.YELLOW)
77
+ typer.echo("sudo systemctl daemon-reload")
78
+ typer.echo("sudo systemctl enable smart-deploy")
79
+ typer.echo("sudo systemctl start smart-deploy")
80
+ typer.echo("sudo systemctl status smart-deploy")
81
+
82
+ if __name__ == "__main__":
83
+ app()
@@ -0,0 +1,44 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ # تعیین مسیر ذخیره‌سازی اطلاعات پروژه‌ها در دایرکتوری کاربر تا با حذف برنامه دیتابیس پاک نشود
6
+ CONFIG_DIR = Path.home() / ".smart-deploy"
7
+ DB_FILE = CONFIG_DIR / "projects.json"
8
+
9
+ def init_db():
10
+ """Initialize the database directory and file if they don't exist."""
11
+ CONFIG_DIR.mkdir(exist_ok=True)
12
+ if not DB_FILE.exists():
13
+ with open(DB_FILE, "w") as f:
14
+ json.dump({}, f)
15
+
16
+ def load_projects() -> dict:
17
+ """Load all projects from the JSON database."""
18
+ init_db()
19
+ with open(DB_FILE, "r") as f:
20
+ return json.load(f)
21
+
22
+ def save_project(repo_url: str, path: str, token: Optional[str] = None, restart_cmd: Optional[str] = None) -> str:
23
+ """Save a new project to the database and return the extracted repository name."""
24
+ projects = load_projects()
25
+
26
+ # استخراج شناسه یکتای ریپازیتوری از لینک برای استفاده به عنوان کلید اصلی
27
+ repo_name = repo_url.replace("https://github.com/", "").replace(".git", "")
28
+
29
+ projects[repo_name] = {
30
+ "repo_url": repo_url,
31
+ "path": path,
32
+ "token": token,
33
+ "restart_cmd": restart_cmd
34
+ }
35
+
36
+ with open(DB_FILE, "w") as f:
37
+ json.dump(projects, f, indent=4)
38
+
39
+ return repo_name
40
+
41
+ def get_project(repo_name: str) -> Optional[dict]:
42
+ """Retrieve project configuration by repository name."""
43
+ projects = load_projects()
44
+ return projects.get(repo_name)
@@ -0,0 +1,61 @@
1
+ import os
2
+ import subprocess
3
+ from typing import Optional
4
+
5
+ def run_command(command: str, cwd: str) -> bool:
6
+ """Execute a shell command in the specified directory and print the output."""
7
+ print(f"Executing: [{command}] in {cwd}")
8
+ try:
9
+ result = subprocess.run(
10
+ command,
11
+ shell=True,
12
+ cwd=cwd,
13
+ stdout=subprocess.PIPE,
14
+ stderr=subprocess.PIPE,
15
+ text=True
16
+ )
17
+ if result.returncode == 0:
18
+ print(f"Success:\n{result.stdout.strip()}")
19
+ return True
20
+ else:
21
+ print(f"Error:\n{result.stderr.strip()}")
22
+ return False
23
+ except Exception as e:
24
+ print(f"System Exception: {e}")
25
+ return False
26
+
27
+ def pull_repo(path: str, token: Optional[str], repo_url: str) -> bool:
28
+ """Pull the latest changes from the remote repository."""
29
+ if token:
30
+ # تزریق مستقیم توکن به آدرس ریپازیتوری برای دور زدن نیاز به احراز هویت تعاملی در سرور
31
+ url_without_protocol = repo_url.replace("https://", "")
32
+ auth_url = f"https://{token}@{url_without_protocol}"
33
+ run_command(f"git remote set-url origin {auth_url}", path)
34
+
35
+ return run_command("git pull", path)
36
+
37
+ def detect_and_deploy(path: str, restart_cmd: Optional[str] = None):
38
+ """Analyze the project structure and run appropriate build commands."""
39
+
40
+ # بررسی وجود فایل‌های پایه جاوا اسکریپت برای تشخیص فریم‌ورک‌های مبتنی بر Node
41
+ if os.path.exists(os.path.join(path, "package.json")):
42
+ print("Framework detected: Node.js")
43
+ run_command("npm install", path)
44
+
45
+ if os.path.exists(os.path.join(path, "next.config.js")) or os.path.exists(os.path.join(path, "next.config.mjs")):
46
+ print("Framework detected: Next.js")
47
+ run_command("npm run build", path)
48
+
49
+ # بررسی ساختار پروژه‌های پایتونی
50
+ elif os.path.exists(os.path.join(path, "requirements.txt")):
51
+ print("Framework detected: Python")
52
+ # اولویت استفاده از محیط مجازی در صورت وجود برای جلوگیری از تداخل پکیج‌ها در سرور
53
+ pip_cmd = "venv/bin/pip" if os.path.exists(os.path.join(path, "venv")) else "pip"
54
+ run_command(f"{pip_cmd} install -r requirements.txt", path)
55
+
56
+ # اجرای دستور ری‌استارت شخصی‌سازی شده در صورت تعریف توسط کاربر
57
+ if restart_cmd:
58
+ print(f"Executing custom restart command: {restart_cmd}")
59
+ run_command(restart_cmd, path)
60
+ else:
61
+ print("No custom restart command provided. Deployment cycle completed.")
@@ -0,0 +1,46 @@
1
+ import requests
2
+
3
+ def setup_webhook(repo_name: str, token: str, server_url: str) -> bool:
4
+ """Configure a push webhook on the target GitHub repository."""
5
+ api_url = f"https://api.github.com/repos/{repo_name}/hooks"
6
+ target_webhook_url = f"{server_url}/webhook"
7
+
8
+ headers = {
9
+ "Authorization": f"token {token}",
10
+ "Accept": "application/vnd.github.v3+json"
11
+ }
12
+
13
+ payload = {
14
+ "name": "web",
15
+ "active": True,
16
+ "events": ["push"],
17
+ "config": {
18
+ "url": target_webhook_url,
19
+ "content_type": "json",
20
+ "insecure_ssl": "1" # مجاز کردن کانکشن‌های بدون SSL برای سرورهای تستی
21
+ }
22
+ }
23
+
24
+ # بررسی وب‌هوک‌های فعلی ریپازیتوری برای جلوگیری از ثبت آدرس تکراری
25
+ try:
26
+ response = requests.get(api_url, headers=headers)
27
+ if response.status_code == 200:
28
+ for hook in response.json():
29
+ if hook.get("config", {}).get("url") == target_webhook_url:
30
+ print(f"Webhook configuration already exists for {target_webhook_url}")
31
+ return True
32
+ except Exception as e:
33
+ print(f"Failed to fetch existing webhooks: {e}")
34
+
35
+ # ثبت وب‌هوک جدید روی ریپازیتوری
36
+ try:
37
+ response = requests.post(api_url, headers=headers, json=payload)
38
+ if response.status_code == 201:
39
+ print("Webhook successfully registered on GitHub.")
40
+ return True
41
+ else:
42
+ print(f"Webhook registration failed [{response.status_code}]: {response.text}")
43
+ return False
44
+ except Exception as e:
45
+ print(f"Exception occurred during webhook registration: {e}")
46
+ return False
@@ -0,0 +1,40 @@
1
+ from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
2
+ from .database import get_project
3
+ from .detector import pull_repo, detect_and_deploy
4
+
5
+ app = FastAPI(title="Smart Deploy Webhook Receiver")
6
+
7
+ def handle_deployment(project_info: dict):
8
+ """Background task to handle the deployment process."""
9
+ path = project_info.get("path")
10
+ token = project_info.get("token")
11
+ repo_url = project_info.get("repo_url")
12
+ restart_cmd = project_info.get("restart_cmd")
13
+
14
+ print(f"--- Initiating deployment pipeline for {repo_url} ---")
15
+
16
+ if pull_repo(path, token, repo_url):
17
+ detect_and_deploy(path, restart_cmd)
18
+ print("--- Deployment pipeline executed successfully ---")
19
+ else:
20
+ print("--- Deployment pipeline aborted: Pull operation failed ---")
21
+
22
+ @app.post("/webhook")
23
+ async def github_webhook(request: Request, background_tasks: BackgroundTasks):
24
+ """Endpoint for receiving GitHub webhook push events."""
25
+ payload = await request.json()
26
+
27
+ # استخراج نام کامل ریپازیتوری از دیتای ارسالی گیت‌هاب جهت تطبیق با دیتابیس محلی
28
+ try:
29
+ repo_name = payload["repository"]["full_name"]
30
+ except KeyError:
31
+ raise HTTPException(status_code=400, detail="Malformed GitHub payload structure.")
32
+
33
+ project_info = get_project(repo_name)
34
+ if not project_info:
35
+ raise HTTPException(status_code=404, detail=f"Repository '{repo_name}' is not registered in Smart Deploy.")
36
+
37
+ # اجرای پروسه در پس‌زمینه تا سرور گیت‌هاب با خطای Timeout به دلیل طولانی شدن زمان بیلد مواجه نشود
38
+ background_tasks.add_task(handle_deployment, project_info)
39
+
40
+ return {"status": "Deployment job queued successfully", "repository": repo_name}
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: smart-deploy
3
+ Version: 0.1.0
4
+ Requires-Dist: typer
5
+ Requires-Dist: fastapi
6
+ Requires-Dist: uvicorn
7
+ Requires-Dist: requests
8
+ Requires-Dist: pydantic
9
+ Dynamic: requires-dist
@@ -0,0 +1,14 @@
1
+ README.md
2
+ setup.py
3
+ smart_deploy/__init__.py
4
+ smart_deploy/cli.py
5
+ smart_deploy/database.py
6
+ smart_deploy/detector.py
7
+ smart_deploy/github_api.py
8
+ smart_deploy/server.py
9
+ smart_deploy.egg-info/PKG-INFO
10
+ smart_deploy.egg-info/SOURCES.txt
11
+ smart_deploy.egg-info/dependency_links.txt
12
+ smart_deploy.egg-info/entry_points.txt
13
+ smart_deploy.egg-info/requires.txt
14
+ smart_deploy.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ smart-deploy = smart_deploy.cli:app
@@ -0,0 +1,5 @@
1
+ typer
2
+ fastapi
3
+ uvicorn
4
+ requests
5
+ pydantic
@@ -0,0 +1 @@
1
+ smart_deploy