package main import ( "canguidev/shelfy/internal/db" "canguidev/shelfy/internal/routes" "canguidev/shelfy/internal/utils" "crypto/ed25519" "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "io" "log" "net" "os" "path/filepath" "strings" "time" "github.com/gin-gonic/gin" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" ) // ---------- SFTP (SSH natif) ---------- func loadOrCreateHostKey(path string) (ssh.Signer, error) { // Si une clé PEM existe déjà, on la lit if _, err := os.Stat(path); err == nil { b, err := os.ReadFile(path) if err != nil { return nil, err } return ssh.ParsePrivateKey(b) } // Sinon on génère une clé ed25519 et on la sauvegarde en PKCS#8 PEM _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, err } pkcs8, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { return nil, err } block := &pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8} pemBytes := pem.EncodeToMemory(block) if err := os.WriteFile(path, pemBytes, 0o600); err != nil { return nil, err } return ssh.ParsePrivateKey(pemBytes) } func startSFTPServer(uploadBase string) { absBase, _ := filepath.Abs(uploadBase) if fi, err := os.Stat(absBase); err != nil || !fi.IsDir() { log.Fatalf("[SFTP] Le dossier %s est manquant ou invalide: %v", absBase, err) } // Clé hôte signer, err := loadOrCreateHostKey("sftp_host_ed25519.pem") if err != nil { log.Fatalf("[SFTP] Host key error: %v", err) } // Config SSH (user/pass) conf := &ssh.ServerConfig{ PasswordCallback: func(meta ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { user := meta.User() remote := meta.RemoteAddr().String() log.Printf("[SFTP] Auth tentative user=%s from=%s", user, remote) if user == "cangui2089" && string(pass) == "GHT30k7!" { return nil, nil } return nil, fmt.Errorf("auth failed") }, } conf.AddHostKey(signer) // Listener TCP avec keep-alive agressif ln, err := net.Listen("tcp", ":2222") if err != nil { log.Fatalf("[SFTP] Listen: %v", err) } log.Println("[SFTP] Écoute sur sftp://cangui2089@0.0.0.0:2222 (base visible: voir montage)") for { nConn, err := ln.Accept() if err != nil { log.Printf("[SFTP] Accept err: %v", err) continue } // Keep-alive TCP if tc, ok := nConn.(*net.TCPConn); ok { _ = tc.SetKeepAlive(true) _ = tc.SetKeepAlivePeriod(15 * time.Second) } go func(conn net.Conn) { // Handshake SSH sshConn, chans, reqs, err := ssh.NewServerConn(conn, conf) if err != nil { log.Printf("[SFTP] Handshake err: %v", err) _ = conn.Close() return } log.Printf("[SFTP] Client connecté: %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion()) // On ignore les global requests go ssh.DiscardRequests(reqs) // Gestion des channels (sessions) for newCh := range chans { if newCh.ChannelType() != "session" { newCh.Reject(ssh.UnknownChannelType, "only session channels are allowed") continue } ch, reqs, err := newCh.Accept() if err != nil { log.Printf("[SFTP] Accept channel: %v", err) continue } // Sur une session, on attend un "subsystem: sftp" go func(in <-chan *ssh.Request) { for req := range in { switch req.Type { case "subsystem": if string(req.Payload[4:]) == "sftp" { // payload = string len + "sftp" // Lance le serveur SFTP minimal (pas d’options avancées) server, err := sftp.NewServer(ch) if err != nil { log.Printf("[SFTP] init error: %v", err) _, _ = ch.Stderr().Write([]byte("sftp init error\n")) _ = ch.Close() return } _ = req.Reply(true, nil) if err := server.Serve(); err == io.EOF { _ = server.Close() } else if err != nil { log.Printf("[SFTP] serve error: %v", err) } return } _ = req.Reply(false, nil) case "exec", "shell", "pty-req": // On n'autorise que le sous-système SFTP _ = req.Reply(false, nil) default: _ = req.Reply(false, nil) } } }(reqs) } }(nConn) } } // ---------- HTTP (Gin) ---------- func startHTTP() { bd := db.InitDB() app := gin.Default() api := app.Group("/api/v1") routes.AddRoutes(api, bd) utils.CreateDefaultFolder(bd) app.Static("/static", "./web") app.NoRoute(func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/api/") { c.JSON(404, gin.H{"error": "Not found"}) return } c.File("./web/index.html") }) log.Println("[HTTP] Serveur Gin sur http://0.0.0.0:8080") _ = app.Run(":8080") } func main() { // SFTP (recommandé pour VLC) go startSFTPServer("upload") // HTTP (ton app) startHTTP() }