"""YakPanel - Crontab API""" import json import tempfile import os from pathlib import Path from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel from app.core.database import get_db from app.core.utils import exec_shell_sync from app.api.auth import get_current_user from app.models.user import User from app.models.crontab import Crontab router = APIRouter(prefix="/crontab", tags=["crontab"]) _CRON_TEMPLATES = Path(__file__).resolve().parent.parent / "data" / "cron_templates.json" @router.get("/templates") async def crontab_templates(current_user: User = Depends(get_current_user)): """YakPanel starter cron templates (edit before apply; no external branding).""" if not _CRON_TEMPLATES.is_file(): return {"templates": []} try: data = json.loads(_CRON_TEMPLATES.read_text(encoding="utf-8")) return {"templates": data if isinstance(data, list) else []} except (json.JSONDecodeError, OSError): return {"templates": []} class CreateCrontabRequest(BaseModel): name: str = "" type: str = "shell" schedule: str execstr: str @router.get("/list") async def crontab_list( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """List cron jobs""" result = await db.execute(select(Crontab).order_by(Crontab.id)) rows = result.scalars().all() return [{"id": r.id, "name": r.name, "type": r.type, "schedule": r.schedule, "execstr": r.execstr} for r in rows] @router.post("/create") async def crontab_create( body: CreateCrontabRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Create cron job""" cron = Crontab(name=body.name, type=body.type, schedule=body.schedule, execstr=body.execstr) db.add(cron) await db.commit() return {"status": True, "msg": "Cron job created", "id": cron.id} @router.post("/apply") async def crontab_apply( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Sync panel cron jobs to system crontab (root)""" result = await db.execute(select(Crontab).order_by(Crontab.id)) rows = result.scalars().all() lines = [ "# YakPanel managed crontab - do not edit manually", "", ] for r in rows: if r.name: lines.append(f"# {r.name}") lines.append(f"{r.schedule} {r.execstr}") lines.append("") content = "\n".join(lines).strip() + "\n" fd, path = tempfile.mkstemp(suffix=".crontab", prefix="cit_") try: os.write(fd, content.encode("utf-8")) os.close(fd) out, err = exec_shell_sync(f"crontab {path}", timeout=10) if err and "error" in err.lower(): raise HTTPException(status_code=500, detail=err.strip() or out.strip()) finally: if os.path.exists(path): os.unlink(path) return {"status": True, "msg": "Crontab applied", "count": len(rows)} @router.put("/{cron_id}") async def crontab_update( cron_id: int, body: CreateCrontabRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Update cron job""" result = await db.execute(select(Crontab).where(Crontab.id == cron_id)) cron = result.scalar_one_or_none() if not cron: raise HTTPException(status_code=404, detail="Cron job not found") cron.name = body.name cron.type = body.type cron.schedule = body.schedule cron.execstr = body.execstr await db.commit() return {"status": True, "msg": "Cron job updated"} @router.delete("/{cron_id}") async def crontab_delete( cron_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Delete cron job""" result = await db.execute(select(Crontab).where(Crontab.id == cron_id)) cron = result.scalar_one_or_none() if not cron: raise HTTPException(status_code=404, detail="Cron job not found") await db.delete(cron) await db.commit() return {"status": True, "msg": "Cron job deleted"}