shelfy-v2/main.go
2025-08-17 15:26:00 +02:00

191 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 doptions 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()
}