fix: improve error handling and refactor app initialization process in HTTP handler

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Jakub Dorfman 2026-04-29 18:36:11 +02:00
parent 435be527d7
commit 197926a8a8

View file

@ -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":