package controllers import ( "encoding/json" "nodemaster/models" beego "github.com/beego/beego/v2/server/web" ) // NodeController manages the current host node. type NodeController struct { beego.Controller } // @Title GetNode // @Description get current node info including runtime config_file path // @Success 200 {object} models.NodeInfo // @router / [get] func (c *NodeController) Get() { n := models.GetLocalNode() c.Data["json"] = map[string]interface{}{ "hostname": n.Hostname, "description": n.Description, "update_history": n.UpdateHistory, "backup": n.Backup, "config_file": models.ConfigPath(), "restic_password_set": n.ResticPassword != "", } c.ServeJSON() } // @Title UpdateNode // @Description update current node hostname, description, or restic_password (the repository password used to encrypt/decrypt all restic backup targets on this node) // @Param body body models.NodeInfo true "fields to update" // @Success 200 {string} update success // @Failure 400 invalid body // @router / [put] func (c *NodeController) Put() { var n models.NodeInfo if err := json.Unmarshal(c.Ctx.Input.RequestBody, &n); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } if err := models.UpdateLocalNode(n); err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Data["json"] = map[string]string{"message": "update success"} c.ServeJSON() } // @Title GetUpdates // @Description list update history // @Success 200 {object} []models.UpdateRecord // @router /updates [get] func (c *NodeController) GetUpdates() { c.Data["json"] = models.GetUpdateHistory() c.ServeJSON() } // @Title GetUpdate // @Description get a single update record // @Param id path string true "update record id" // @Success 200 {object} models.UpdateRecord // @Failure 404 not found // @router /updates/:id [get] func (c *NodeController) GetUpdate() { id := c.Ctx.Input.Param(":id") r, err := models.GetUpdateRecord(id) if err != nil { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Data["json"] = r c.ServeJSON() } // @Title AddUpdate // @Description record a new update entry // @Param body body models.UpdateRecord true "update record" // @Success 201 {object} models.UpdateRecord // @Failure 400 invalid body // @router /updates [post] func (c *NodeController) AddUpdate() { var r models.UpdateRecord if err := json.Unmarshal(c.Ctx.Input.RequestBody, &r); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } record, err := models.AddUpdateRecord(r) if err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Ctx.Output.SetStatus(201) c.Data["json"] = record c.ServeJSON() } // @Title DeleteUpdate // @Description delete an update record // @Param id path string true "update record id" // @Success 200 {string} delete success // @Failure 404 not found // @router /updates/:id [delete] func (c *NodeController) DeleteUpdate() { id := c.Ctx.Input.Param(":id") if err := models.DeleteUpdateRecord(id); err != nil { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Data["json"] = map[string]string{"message": "delete success"} c.ServeJSON() } // @Title GetBackup // @Description get node backup configuration // @Success 200 {object} models.BackupConfig // @router /backup [get] func (c *NodeController) GetBackup() { c.Data["json"] = models.GetNodeBackupConfig() c.ServeJSON() } // @Title UpdateBackup // @Description update node backup configuration // @Param body body models.BackupConfig true "backup config" // @Success 200 {string} update success // @Failure 400 invalid body // @router /backup [put] func (c *NodeController) PutBackup() { var bc models.BackupConfig if err := json.Unmarshal(c.Ctx.Input.RequestBody, &bc); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } if err := models.UpdateNodeBackupConfig(bc); err != nil { c.Ctx.Output.SetStatus(500) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Data["json"] = map[string]string{"message": "update success"} c.ServeJSON() } // @Title Scan // @Description scan compose folders and index discovered services; accepts optional {"folder":"/path"} body // @Param body body object false "optional folder override" // @Success 200 {object} models.ScanResult // @router /scan [post] func (c *NodeController) Scan() { var req struct { Folder string `json:"folder"` } _ = json.Unmarshal(c.Ctx.Input.RequestBody, &req) result := models.ScanAll(req.Folder) c.Data["json"] = result c.ServeJSON() } // @Title RunBackup // @Description trigger an immediate node backup across all targets (asynchronous) // @Success 202 {string} backup started // @Failure 400 no targets configured // @router /backup/run [post] func (c *NodeController) RunBackup() { bc := models.GetNodeBackupConfig() if len(bc.Targets) == 0 { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]string{"error": "no backup targets configured"} c.ServeJSON() return } c.Ctx.Output.SetStatus(202) c.Data["json"] = map[string]string{"message": "backup started"} c.ServeJSON() go func() { _ = models.RunNodeBackup(nil) }() } // @Title GetBackupTargets // @Description list node backup target definitions // @Success 200 {object} []models.BackupTarget // @router /backup/targets [get] func (c *NodeController) GetBackupTargets() { c.Data["json"] = models.GetNodeBackupTargets() c.ServeJSON() } // @Title AddBackupTarget // @Description define a new node backup target // @Param body body models.BackupTarget true "backup target definition" // @Success 201 {object} models.BackupTarget // @Failure 400 invalid body // @router /backup/targets [post] func (c *NodeController) AddBackupTarget() { var t models.BackupTarget if err := json.Unmarshal(c.Ctx.Input.RequestBody, &t); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } created, err := models.AddNodeBackupTarget(t) if err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Ctx.Output.SetStatus(201) c.Data["json"] = created c.ServeJSON() } // @Title GetBackupTarget // @Description get a node backup target by id // @Param tid path string true "backup target id" // @Success 200 {object} models.BackupTarget // @Failure 404 not found // @router /backup/targets/:tid [get] func (c *NodeController) GetBackupTarget() { tid := c.Ctx.Input.Param(":tid") t, err := models.GetNodeBackupTarget(tid) if err != nil { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Data["json"] = t c.ServeJSON() } // @Title PutBackupTarget // @Description update a node backup target definition // @Param tid path string true "backup target id" // @Param body body models.BackupTarget true "updated backup target" // @Success 200 {object} models.BackupTarget // @Failure 400 invalid body // @Failure 404 not found // @router /backup/targets/:tid [put] func (c *NodeController) PutBackupTarget() { tid := c.Ctx.Input.Param(":tid") var t models.BackupTarget if err := json.Unmarshal(c.Ctx.Input.RequestBody, &t); err != nil { c.Ctx.Output.SetStatus(400) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } updated, err := models.UpdateNodeBackupTarget(tid, t) if err != nil { status := 404 if err.Error() != "backup target not found" { status = 400 } c.Ctx.Output.SetStatus(status) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Data["json"] = updated c.ServeJSON() } // @Title DeleteBackupTarget // @Description delete a node backup target definition // @Param tid path string true "backup target id" // @Success 200 {string} delete success // @Failure 404 not found // @router /backup/targets/:tid [delete] func (c *NodeController) DeleteBackupTarget() { tid := c.Ctx.Input.Param(":tid") if err := models.DeleteNodeBackupTarget(tid); err != nil { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Data["json"] = map[string]string{"message": "delete success"} c.ServeJSON() } // @Title RunBackupTarget // @Description trigger an immediate backup for a single node target (asynchronous) // @Param tid path string true "backup target id" // @Success 202 {string} backup started // @Failure 404 not found // @router /backup/targets/:tid/run [post] func (c *NodeController) RunBackupTarget() { tid := c.Ctx.Input.Param(":tid") if _, err := models.GetNodeBackupTarget(tid); err != nil { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } c.Ctx.Output.SetStatus(202) c.Data["json"] = map[string]string{"message": "backup started"} c.ServeJSON() go func() { _ = models.RunNodeBackup([]string{tid}) }() } // @Title GetBackupTargetProgress // @Description get the current/last run state for a node backup target // @Param tid path string true "backup target id" // @Success 200 {object} models.RunState // @Failure 404 not found // @router /backup/targets/:tid/progress [get] func (c *NodeController) GetBackupTargetProgress() { tid := c.Ctx.Input.Param(":tid") if _, err := models.GetNodeBackupTarget(tid); err != nil { c.Ctx.Output.SetStatus(404) c.Data["json"] = map[string]string{"error": err.Error()} c.ServeJSON() return } state, ok := models.GetProgress("node", tid) if !ok { state = models.RunState{TargetID: tid} } c.Data["json"] = state c.ServeJSON() } // @Title GetBackupProgress // @Description get the current/last run state for every node backup target // @Success 200 {object} []models.RunState // @router /backup/progress [get] func (c *NodeController) GetBackupProgress() { c.Data["json"] = models.GetProgressForScope("node") c.ServeJSON() }