"""YakPanel - Crontab API""" import tempfile import os 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"]) 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"}