Installer-Paket System: Backend speichert/served ZIP, Frontend Upload+Download, install.sh auto-download von Backend statt Git
This commit is contained in:
@@ -68,6 +68,11 @@ func (h *Handler) SetupRoutes(mux *http.ServeMux, hub *ws.Hub) {
|
||||
mux.HandleFunc("DELETE /api/v1/agents/{id}/request-update", h.cancelUpdate)
|
||||
mux.HandleFunc("POST /api/v1/agents/request-update-all", h.requestUpdateAll)
|
||||
|
||||
// Installer Routes
|
||||
mux.HandleFunc("GET /api/v1/installer", h.getInstallerInfo)
|
||||
mux.HandleFunc("POST /api/v1/installer/upload", h.uploadInstaller)
|
||||
mux.HandleFunc("GET /api/v1/installer/download", h.downloadInstaller)
|
||||
|
||||
// WebSocket und Tunnel-Routes
|
||||
h.setupTunnelRoutes(mux, hub)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// POST /api/v1/installer/upload?platform=freebsd — Installer-Paket hochladen (ZIP oder Script)
|
||||
func (h *Handler) uploadInstaller(w http.ResponseWriter, r *http.Request) {
|
||||
platform := r.URL.Query().Get("platform")
|
||||
if platform == "" {
|
||||
platform = "freebsd"
|
||||
}
|
||||
validPlatforms := map[string]bool{"freebsd": true, "linux": true}
|
||||
if !validPlatforms[platform] {
|
||||
writeError(w, http.StatusBadRequest, "Ungueltige Plattform (freebsd, linux)")
|
||||
return
|
||||
}
|
||||
|
||||
// Max 50MB
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
|
||||
|
||||
// Multipart oder raw body
|
||||
var data []byte
|
||||
var filename string
|
||||
var err error
|
||||
|
||||
if ct := r.Header.Get("Content-Type"); ct != "" && len(ct) > 19 && ct[:19] == "multipart/form-data" {
|
||||
file, header, ferr := r.FormFile("file")
|
||||
if ferr != nil {
|
||||
writeError(w, http.StatusBadRequest, "Datei fehlt im Upload")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
data, err = io.ReadAll(file)
|
||||
filename = header.Filename
|
||||
} else {
|
||||
data, err = io.ReadAll(r.Body)
|
||||
filename = r.URL.Query().Get("filename")
|
||||
if filename == "" {
|
||||
filename = fmt.Sprintf("installer-%s.zip", platform)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil || len(data) == 0 {
|
||||
writeError(w, http.StatusBadRequest, "Leere oder fehlerhafte Datei")
|
||||
return
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(data)
|
||||
hashStr := hex.EncodeToString(hash[:])
|
||||
|
||||
if err := h.db.SaveInstaller(platform, filename, hashStr, data); err != nil {
|
||||
log.Printf("Installer speichern fehlgeschlagen: %v", err)
|
||||
writeError(w, http.StatusInternalServerError, "Speichern fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Installer fuer %s hochgeladen: %s (%d bytes, SHA256: %s)", platform, filename, len(data), hashStr[:16])
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"platform": platform,
|
||||
"filename": filename,
|
||||
"sha256": hashStr,
|
||||
"size": len(data),
|
||||
"message": "Installer hochgeladen",
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/installer — Installer-Info (alle Plattformen)
|
||||
func (h *Handler) getInstallerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
all, err := h.db.GetAllInstallerInfo()
|
||||
if err != nil || len(all) == 0 {
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"installers": []interface{}{},
|
||||
})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"installers": all,
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/installer/download?platform=freebsd — Installer herunterladen
|
||||
func (h *Handler) downloadInstaller(w http.ResponseWriter, r *http.Request) {
|
||||
platform := r.URL.Query().Get("platform")
|
||||
if platform == "" {
|
||||
platform = "freebsd"
|
||||
}
|
||||
|
||||
filename, hashStr, data, err := h.db.GetInstaller(platform)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, fmt.Sprintf("Kein Installer fuer Plattform '%s' vorhanden", platform))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
||||
w.Header().Set("X-Installer-SHA256", hashStr)
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
|
||||
w.Write(data)
|
||||
}
|
||||
@@ -191,6 +191,17 @@ func (d *Database) migrate() error {
|
||||
last_used TIMESTAMPTZ
|
||||
)`)
|
||||
|
||||
// Installer-Pakete (install.sh + Plugin als ZIP pro Plattform)
|
||||
d.db.Exec(`CREATE TABLE IF NOT EXISTS installers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
platform TEXT NOT NULL UNIQUE,
|
||||
filename TEXT NOT NULL,
|
||||
data BYTEA NOT NULL,
|
||||
sha256 TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`)
|
||||
|
||||
// Retention Policy (90 Tage)
|
||||
d.setupRetention()
|
||||
|
||||
@@ -604,6 +615,57 @@ func (d *Database) GetAllFirmwareInfo() ([]map[string]interface{}, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// --- Installer ---
|
||||
|
||||
func (d *Database) SaveInstaller(platform, filename, sha256hash string, data []byte) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT INTO installers (platform, filename, data, sha256, size, uploaded_at)
|
||||
VALUES ($1, $2, $3, $4, $5, NOW())
|
||||
ON CONFLICT (platform) DO UPDATE SET filename=$2, data=$3, sha256=$4, size=$5, uploaded_at=NOW()
|
||||
`, platform, filename, data, sha256hash, len(data))
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) GetInstaller(platform string) (string, string, []byte, error) {
|
||||
var filename, sha256hash string
|
||||
var data []byte
|
||||
err := d.db.QueryRow("SELECT filename, sha256, data FROM installers WHERE platform = $1", platform).Scan(&filename, &sha256hash, &data)
|
||||
return filename, sha256hash, data, err
|
||||
}
|
||||
|
||||
func (d *Database) GetInstallerInfo(platform string) (string, string, int, string, error) {
|
||||
var filename, sha256hash string
|
||||
var size int
|
||||
var uploadedAt time.Time
|
||||
err := d.db.QueryRow("SELECT filename, sha256, size, uploaded_at FROM installers WHERE platform = $1", platform).Scan(&filename, &sha256hash, &size, &uploadedAt)
|
||||
return filename, sha256hash, size, uploadedAt.Format(time.RFC3339), err
|
||||
}
|
||||
|
||||
func (d *Database) GetAllInstallerInfo() ([]map[string]interface{}, error) {
|
||||
rows, err := d.db.Query("SELECT platform, filename, sha256, size, uploaded_at FROM installers ORDER BY platform")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var results []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var platform, filename, sha256hash string
|
||||
var size int
|
||||
var uploadedAt time.Time
|
||||
if err := rows.Scan(&platform, &filename, &sha256hash, &size, &uploadedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, map[string]interface{}{
|
||||
"platform": platform,
|
||||
"filename": filename,
|
||||
"sha256": sha256hash,
|
||||
"size": size,
|
||||
"uploaded_at": uploadedAt,
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (d *Database) DeleteAgent(id string) (bool, error) {
|
||||
res, err := d.db.Exec("DELETE FROM agents WHERE id = $1", id)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user