Files
lpra/internal/server/sshfs.go
T

106 lines
3.6 KiB
Go

package server
import (
"encoding/json"
"io"
"net/http"
"strings"
"github.com/x-grk/longpolling-remote-access/internal/proto"
)
// Files on an SSH host through a machine, for the panel's file panes: the
// machine's agent keeps the SSH connection (open gives its id) and runs the
// operations there. Downloads and uploads go in the same pieces as a
// machine's own files.
// POST /api/panel/agents/{id}/sshfs/open - {"ssh": {...}} → {conn, home}.
func (s *Server) handleSSHFSOpen(w http.ResponseWriter, r *http.Request) {
agentID := r.PathValue("id")
var in struct {
SSH *proto.SSHTarget `json:"ssh"`
}
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&in); err != nil || in.SSH == nil {
writeJSON(w, proto.TaskResult{Error: "не указан хост"})
return
}
t := in.SSH
t.Host, t.User = strings.TrimSpace(t.Host), strings.TrimSpace(t.User)
if t.Port == 0 {
t.Port = 22
}
if t.Host == "" || t.User == "" || t.Port < 1 || t.Port > 65535 {
writeJSON(w, proto.TaskResult{Error: "нужны хост, порт и пользователь"})
return
}
if !s.fsAgent(w, agentID, true) {
return
}
res, err := s.waitTask(r.Context(), agentID, proto.TaskSSHFSOpen, proto.SSHFSArgs{SSH: t}, fsTaskTimeout)
if err != nil {
res = proto.TaskResult{Error: err.Error()}
}
if unknownOp(res) {
res = proto.TaskResult{Error: "агент не умеет файлы по SSH - обновите его"}
}
if res.OK {
if admin := s.currentAdmin(r); admin != nil {
s.db.Audit(admin.ID, agentID, "sshfs.open:"+t.User+"@"+t.Host)
}
}
writeJSON(w, res)
}
// sshfsChanges are the operations that change files, for the audit log.
var sshfsChanges = map[string]bool{"write": true, "mkdir": true, "delete": true, "deltree": true, "move": true, "copy": true, "chmod": true}
// POST /api/panel/agents/{id}/sshfs/{conn}/{op} - an operation on the host;
// the body is in the shape of the machine's own fs.{op}. "close" drops the
// connection.
func (s *Server) handleSSHFSOp(w http.ResponseWriter, r *http.Request) {
agentID, conn, op := r.PathValue("id"), r.PathValue("conn"), r.PathValue("op")
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 64<<20))
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if !s.fsAgent(w, agentID, true) {
return
}
task, args := proto.TaskSSHFSOp, proto.SSHFSArgs{Conn: conn, Op: op, Args: json.RawMessage(body)}
if op == "close" {
task, args = proto.TaskSSHFSClose, proto.SSHFSArgs{Conn: conn}
}
if len(args.Args) == 0 {
args.Args = json.RawMessage("{}")
}
if sshfsChanges[op] {
var d struct{ Path, Src, Dst string }
_ = json.Unmarshal(body, &d)
if admin := s.currentAdmin(r); admin != nil {
s.db.Audit(admin.ID, agentID, "sshfs."+op+":"+strings.TrimSpace(d.Path+" "+d.Src+" "+d.Dst))
}
}
res, err := s.waitTask(r.Context(), agentID, task, args, fsTaskTimeout)
if err != nil {
res = proto.TaskResult{Error: err.Error()}
}
if unknownOp(res) && op != "close" {
res = proto.TaskResult{Error: "агент не умеет файлы по SSH - обновите его"}
}
writeJSON(w, res)
}
// GET /api/panel/agents/{id}/sshfs/{conn}/download?path=...[&inline=1]
func (s *Server) handleSSHFSDownload(w http.ResponseWriter, r *http.Request) {
s.download(w, r, proto.TaskSSHFSSend, r.PathValue("conn"))
}
// POST /api/panel/agents/{id}/sshfs/{conn}/upload?path=...&size=N
func (s *Server) handleSSHFSUpload(w http.ResponseWriter, r *http.Request) {
if admin := s.currentAdmin(r); admin != nil {
s.db.Audit(admin.ID, r.PathValue("id"), "sshfs.upload:"+r.URL.Query().Get("path"))
}
s.uploadStart(w, r, proto.TaskSSHFSPut, r.PathValue("conn"))
}