diff --git a/Dockerfile b/Dockerfile index c5439b2..abb5262 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,23 +2,14 @@ FROM python:3.10-slim ENV PYTHONUNBUFFERED 1 -WORKDIR /code +WORKDIR /app -RUN apt-get update \ - && apt-get install -y curl unzip gcc python3-dev \ - && rm -rf /var/lib/apt/lists/* +COPY . /app -RUN bash -c "$(curl -L https://github.com/Gozargah/Marzban-scripts/raw/master/install_latest_xray.sh)" +RUN apt-get update -y \ + && apt-get install npm make -y --no-install-recommends \ + && make dashboard-build \ + && rm -rf /var/lib/apt/lists/* \ + && pip install --no-cache-dir --upgrade -r /app/requirements.txt -COPY ./requirements.txt /code/ -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -COPY . /code - -RUN apt-get remove -y curl unzip gcc python3-dev - -RUN ln -s /code/marzban-cli.py /usr/bin/marzban-cli \ - && chmod +x /usr/bin/marzban-cli \ - && marzban-cli completion install --shell bash - -CMD ["bash", "-c", "alembic upgrade head; python main.py"] \ No newline at end of file +CMD ["make", "start"] diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py deleted file mode 100644 index b49a13b..0000000 --- a/app/dashboard/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import subprocess -from pathlib import Path - -from config import DEBUG, VITE_BASE_API - -path = '/dashboard/' -base_dir = Path(__file__).parent -build_dir = base_dir / 'build' - - -def build(): - proc = subprocess.Popen( - ['npm', 'run', 'build', '--', '--base', path, '--outDir', build_dir], - env={**os.environ, 'VITE_BASE_API': VITE_BASE_API}, - cwd=base_dir - ) - proc.wait() - with open(build_dir / 'index.html', 'r') as file: - html = file.read() - with open(build_dir / '404.html', 'w') as file: - file.write(html) \ No newline at end of file diff --git a/app/marzneshin.py b/app/marzneshin.py index 941754c..2e823f5 100644 --- a/app/marzneshin.py +++ b/app/marzneshin.py @@ -10,20 +10,21 @@ from fastapi import FastAPI, Request, status from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import HTMLResponse -from fastapi.responses import JSONResponse +from fastapi.responses import HTMLResponse, JSONResponse from starlette.staticfiles import StaticFiles -from uvicorn import Server, Config +from uvicorn import Config, Server import config from app.templates import render_template -from config import DOCS, DEBUG, UVICORN_SSL_KEYFILE, UVICORN_SSL_CERTFILE, UVICORN_UDS, UVICORN_PORT, UVICORN_HOST -from config import HOME_PAGE_TEMPLATE +from config import (DEBUG, DOCS, HOME_PAGE_TEMPLATE, UVICORN_HOST, + UVICORN_PORT, UVICORN_SSL_CERTFILE, UVICORN_SSL_KEYFILE, + UVICORN_UDS) + from . import __version__, telegram -from .dashboard import build_dir, build, base_dir from .routes import api_router -from .tasks import record_user_usages, review_users, reset_user_data_usage, \ - record_realtime_bandwidth, send_notifications, delete_expired_reminders, nodes_startup +from .tasks import (delete_expired_reminders, nodes_startup, + record_realtime_bandwidth, record_user_usages, + reset_user_data_usage, review_users, send_notifications) logger = logging.getLogger(__name__) dashboard_path = "/dashboard/" @@ -53,14 +54,21 @@ app.add_middleware( ) scheduler = AsyncIOScheduler(timezone="UTC") -scheduler.add_job(record_user_usages, 'interval', coalesce=True, seconds=30) -scheduler.add_job(review_users, 'interval', seconds=600, coalesce=True, max_instances=1) -scheduler.add_job(reset_user_data_usage, 'interval', coalesce=True, hours=1) -scheduler.add_job(record_realtime_bandwidth, 'interval', seconds=1, coalesce=True, max_instances=1) +scheduler.add_job(record_user_usages, "interval", coalesce=True, seconds=30) +scheduler.add_job(review_users, "interval", seconds=600, coalesce=True, max_instances=1) +scheduler.add_job(reset_user_data_usage, "interval", coalesce=True, hours=1) +scheduler.add_job( + record_realtime_bandwidth, "interval", seconds=1, coalesce=True, max_instances=1 +) if config.WEBHOOK_ADDRESS: scheduler.add_job(send_notifications, "interval", seconds=30, replace_existing=True) - scheduler.add_job(delete_expired_reminders, "interval", hours=2, start_date=dt.utcnow() + td(minutes=1)) + scheduler.add_job( + delete_expired_reminders, + "interval", + hours=2, + start_date=dt.utcnow() + td(minutes=1), + ) @app.on_event("startup") @@ -87,22 +95,10 @@ def validation_exception_handler(request: Request, exc: RequestValidationError): async def main(): - if DEBUG: - proc = await asyncio.create_subprocess_exec( - 'npm', - 'run', 'dev', '--', '--host', '0.0.0.0', '--base', - dashboard_path, '--clearScreen', 'false', - env={**os.environ, 'VITE_BASE_API': config.VITE_BASE_API}, - cwd=base_dir - ) - atexit.register(proc.terminate) - else: - if not build_dir.is_dir(): - build() - + if not DEBUG: app.mount( "/dashboard/", - StaticFiles(directory=build_dir, html=True), + StaticFiles(directory="app/dashboard/dist", html=True), name="dashboard" ) scheduler.start() diff --git a/build_dashboard.sh b/build_dashboard.sh deleted file mode 100755 index b7aeea6..0000000 --- a/build_dashboard.sh +++ /dev/null @@ -1,3 +0,0 @@ -cd `dirname $0`/app/dashboard -VITE_BASE_API=/api/ npm run build --if-present -- --outDir build --base '/dashboard/' -cp ./build/index.html ./build/404.html \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..4b19798 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,8 @@ +services: + marzneshin: + image: marzneshin:latest + restart: always + env_file: .env + network_mode: host + volumes: + - /var/lib/marzneshin:/var/lib/marzneshin diff --git a/docker-compose.yml b/docker-compose.yml index f647593..4b19798 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,8 @@ services: - marzban: - image: gozargah/marzban:latest + marzneshin: + image: marzneshin:latest restart: always env_file: .env network_mode: host volumes: - - /var/lib/marzban:/var/lib/marzban + - /var/lib/marzneshin:/var/lib/marzneshin diff --git a/makefile b/makefile new file mode 100644 index 0000000..f9661dd --- /dev/null +++ b/makefile @@ -0,0 +1,17 @@ + +start: + alembic upgrade head + python main.py + +dashboard-build: + cd app/dashboard/ + npm install --prefix './app/dashboard' + VITE_BASE_API=/api/ npm run build --prefix './app/dashboard' --if-present -- --outDir dist --base '/dashboard/' + ls + cp ./app/dashboard/dist/index.html ./app/dashboard/dist/404.html + +dashboard-dev: + npm run dev \ + -- --host 0.0.0.0 \ + --base ../app/dashboard/ \ + --clearScreen false diff --git a/marzban-cli.py b/marzban-cli.py index f177171..b904ac8 100755 --- a/marzban-cli.py +++ b/marzban-cli.py @@ -1,9 +1,6 @@ #!/usr/bin/env python3 -import os # noqa -import readline # noqa -import sys # noqa -sys.path.insert(0, os.getcwd()) # noqa +import os import typer from typer._completion_shared import Shells @@ -19,32 +16,45 @@ app.add_typer(cli.user.app, name="user") # Hidden completion app -app_completion = typer.Typer(no_args_is_help=True, help="Generate and install completion scripts.", hidden=True) +app_completion = typer.Typer( + no_args_is_help=True, help="Generate and install completion scripts.", hidden=True +) app.add_typer(app_completion, name="completion") def get_default_shell() -> Shells: - shell = os.environ.get('SHELL') + """ + Find the default shell + """ + shell = os.environ.get("SHELL") if shell: - shell = shell.split('/')[-1] + shell = shell.split("/")[-1] if shell in Shells.__members__: return getattr(Shells, shell) return Shells.bash -@app_completion.command(help="Show completion for the specified shell, to copy or customize it.") -def show(ctx: typer.Context, shell: Shells = typer.Option(None, - help="The shell to install completion for.", - case_sensitive=False)) -> None: +@app_completion.command( + help="Show completion for the specified shell, to copy or customize it." +) +def show( + ctx: typer.Context, + shell: Shells = typer.Option( + None, help="The shell to install completion for.", case_sensitive=False + ), +) -> None: if shell is None: shell = get_default_shell() typer.completion.show_callback(ctx, None, shell) @app_completion.command(help="Install completion for the specified shell.") -def install(ctx: typer.Context, shell: Shells = typer.Option(None, - help="The shell to install completion for.", - case_sensitive=False)) -> None: +def install( + ctx: typer.Context, + shell: Shells = typer.Option( + None, help="The shell to install completion for.", case_sensitive=False + ), +) -> None: if shell is None: shell = get_default_shell() typer.completion.install_callback(ctx, None, shell) @@ -52,4 +62,4 @@ def install(ctx: typer.Context, shell: Shells = typer.Option(None, if __name__ == "__main__": typer.completion.completion_init() - app(prog_name=os.environ.get('CLI_PROG_NAME')) + app(prog_name=os.environ.get("CLI_PROG_NAME")) diff --git a/install_service.sh b/tools/service-install.sh similarity index 100% rename from install_service.sh rename to tools/service-install.sh