diff --git a/panel/panel-api.py b/panel/panel-api.py index 3200b83..8bb5c3c 100644 --- a/panel/panel-api.py +++ b/panel/panel-api.py @@ -548,112 +548,116 @@ class Handler(BaseHTTPRequestHandler): self._json(400, {"ok": False, "error": "invalid source_type"}) return - result = run_panelctl(["init", name, domain, port, auth]) - if not result["ok"]: - self._json(400, result) - return - - app, err = read_app_info(name) - if err is not None or app is None: - run_panelctl(["remove", name]) - err_msg = (err or {}).get("error", "unknown error") if err else "app state unavailable" - self._json(500, {"ok": False, "error": f"failed to read app state: {err_msg}"}) - return - - if source_type == "raw": - content = payload.get("compose_content", "") - try: - with open(app["APP_COMPOSE_FILE"], "w", encoding="utf-8") as fh: - fh.write(content) - except OSError as exc: - run_panelctl(["remove", name]) - self._json(500, {"ok": False, "error": f"failed to write compose: {exc}"}) + try: + result = run_panelctl(["init", name, domain, port, auth]) + if not result["ok"]: + self._json(400, result) return - elif source_type == "github": - repo_url = payload.get("github_url", "").strip() - branch = payload.get("github_branch", "main").strip() - pat = payload.get("github_pat", "").strip() - - if not repo_url: + app, err = read_app_info(name) + if err is not None or app is None: run_panelctl(["remove", name]) - self._json(400, {"ok": False, "error": "github_url is required"}) + err_msg = (err or {}).get("error", "unknown error") if err else "app state unavailable" + self._json(500, {"ok": False, "error": f"failed to read app state: {err_msg}"}) return - # Validate URL and extract owner/repo - match = re.match(r'^https?://(?:www\.)?github\.com/([^/]+)/([^/]+?)(?:\.git)?$', repo_url) - if not match: - run_panelctl(["remove", name]) - self._json(400, {"ok": False, "error": "invalid github_url format"}) - return - - owner, repo = match.groups() - - if pat: - # Check GitHub API access - api_url = f"https://api.github.com/repos/{owner}/{repo}" - req = urllib.request.Request(api_url, headers={"Authorization": f"Bearer {pat}"}) + if source_type == "raw": + content = payload.get("compose_content", "") try: - urllib.request.urlopen(req) - except urllib.error.URLError as e: + with open(app["APP_COMPOSE_FILE"], "w", encoding="utf-8") as fh: + fh.write(content) + except OSError as exc: run_panelctl(["remove", name]) - self._json(400, {"ok": False, "error": f"github api check failed: {e.reason}"}) + self._json(500, {"ok": False, "error": f"failed to write compose: {exc}"}) return - # Clone the repository - target_dir = os.path.join(app["APP_STACK_DIR"], "repo") - if os.path.exists(target_dir): - shutil.rmtree(target_dir) + elif source_type == "github": + repo_url = payload.get("github_url", "").strip() + branch = payload.get("github_branch", "main").strip() + pat = payload.get("github_pat", "").strip() - auth_url = repo_url - if pat: - auth_url = auth_url.replace("://", f"://{pat}@") - - if not auth_url.endswith(".git"): - auth_url += ".git" + if not repo_url: + run_panelctl(["remove", name]) + self._json(400, {"ok": False, "error": "github_url is required"}) + return - clone_result = subprocess.run( - ["git", "clone", "--branch", branch, auth_url, target_dir], - capture_output=True, text=True - ) + # Validate URL and extract owner/repo + match = re.match(r'^https?://(?:www\.)?github\.com/([^/]+)/([^/]+?)(?:\.git)?$', repo_url) + if not match: + run_panelctl(["remove", name]) + self._json(400, {"ok": False, "error": "invalid github_url format"}) + return + + owner, repo = match.groups() - if clone_result.returncode != 0: - run_panelctl(["remove", name]) - self._json(400, {"ok": False, "error": f"git clone failed: {clone_result.stderr.strip()}"}) - return + if pat: + # Check GitHub API access + api_url = f"https://api.github.com/repos/{owner}/{repo}" + req = urllib.request.Request(api_url, headers={"Authorization": f"Bearer {pat}"}) + try: + urllib.request.urlopen(req) + except urllib.error.URLError as e: + run_panelctl(["remove", name]) + self._json(400, {"ok": False, "error": f"github api check failed: {e.reason}"}) + return - # Find compose file - compose_path = None - for fname in ["compose.yaml", "docker-compose.yml", "compose.yml", "docker-compose.yaml"]: - candidate = os.path.join(target_dir, fname) - if os.path.isfile(candidate): - compose_path = candidate - break - - if not compose_path: - run_panelctl(["remove", name]) - self._json(400, {"ok": False, "error": "could not find compose file in repository root"}) - return - - # Update manifest - manifest_path = os.path.join(BASE_DIR, "state", "apps", f"{name}.env") - try: - with open(manifest_path, "r", encoding="utf-8") as fh: - lines = fh.readlines() - with open(manifest_path, "w", encoding="utf-8") as fh: - for line in lines: - if line.startswith("APP_COMPOSE_FILE="): - fh.write(f'APP_COMPOSE_FILE="{compose_path}"\n') - else: - fh.write(line) - fh.write(f'APP_REPO_URL="{repo_url}"\n') - fh.write(f'APP_REPO_BRANCH="{branch}"\n') - except OSError as exc: - run_panelctl(["remove", name]) - self._json(500, {"ok": False, "error": f"failed to update manifest: {exc}"}) - return + # Clone the repository + target_dir = os.path.join(app["APP_STACK_DIR"], "repo") + if os.path.exists(target_dir): + shutil.rmtree(target_dir) - self._json(200, {"ok": True, "code": 0, "stdout": "initialized successfully"}) + auth_url = repo_url + if pat: + auth_url = auth_url.replace("://", f"://{pat}@") + + if not auth_url.endswith(".git"): + auth_url += ".git" + + clone_result = subprocess.run( + ["git", "clone", "--branch", branch, auth_url, target_dir], + capture_output=True, text=True + ) + + if clone_result.returncode != 0: + run_panelctl(["remove", name]) + self._json(400, {"ok": False, "error": f"git clone failed: {clone_result.stderr.strip()}"}) + return + + # Find compose file + compose_path = None + for fname in ["compose.yaml", "docker-compose.yml", "compose.yml", "docker-compose.yaml"]: + candidate = os.path.join(target_dir, fname) + if os.path.isfile(candidate): + compose_path = candidate + break + + if not compose_path: + run_panelctl(["remove", name]) + self._json(400, {"ok": False, "error": "could not find compose file in repository root"}) + return + + # Update manifest + manifest_path = os.path.join(BASE_DIR, "state", "apps", f"{name}.env") + try: + with open(manifest_path, "r", encoding="utf-8") as fh: + lines = fh.readlines() + with open(manifest_path, "w", encoding="utf-8") as fh: + for line in lines: + if line.startswith("APP_COMPOSE_FILE="): + fh.write(f'APP_COMPOSE_FILE="{compose_path}"\n') + else: + fh.write(line) + fh.write(f'APP_REPO_URL="{repo_url}"\n') + fh.write(f'APP_REPO_BRANCH="{branch}"\n') + except OSError as exc: + run_panelctl(["remove", name]) + self._json(500, {"ok": False, "error": f"failed to update manifest: {exc}"}) + return + + self._json(200, {"ok": True, "code": 0, "stdout": "initialized successfully"}) + except Exception as exc: + run_panelctl(["remove", name]) + self._json(500, {"ok": False, "error": f"init failed: {exc}"}) return if len(parts) >= 3 and parts[0] == "apps":