Config-Backup Feature: Agent liest config.xml, Backend speichert versioniert mit Deduplizierung
- Agent: neuer 'backup' Command (SHA256 Hash, Base64 Transfer) - Backend: config_backups Tabelle in SQLite - API: trigger, list, download (JSON/XML), delete, diff - Deduplizierung: gleicher Hash = kein neuer Eintrag - WebSocket ReadLimit auf 4MB erhoeht (grosse config.xml) - README aktualisiert mit Backup-Docs
This commit is contained in:
@@ -46,6 +46,19 @@ func (d *Database) migrate() error {
|
||||
last_heartbeat TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS config_backups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
agent_id TEXT NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
config_xml TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_config_backups_agent ON config_backups(agent_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_config_backups_hash ON config_backups(agent_id, hash);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_data (
|
||||
agent_id TEXT PRIMARY KEY,
|
||||
data_json TEXT NOT NULL,
|
||||
@@ -219,6 +232,123 @@ func (d *Database) GetSystemData(agentID string) (*models.SystemData, error) {
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// --- Config Backup Methoden ---
|
||||
|
||||
// SaveConfigBackup speichert ein Config-Backup (dedupliziert nach Hash)
|
||||
func (d *Database) SaveConfigBackup(agentID, hash string, size int, configXML string) (int64, bool, error) {
|
||||
// Pruefen ob ein Backup mit gleichem Hash schon existiert
|
||||
var existingID int64
|
||||
err := d.db.QueryRow("SELECT id FROM config_backups WHERE agent_id = ? AND hash = ? LIMIT 1", agentID, hash).Scan(&existingID)
|
||||
if err == nil {
|
||||
// Gleicher Hash existiert bereits — kein neues Backup noetig
|
||||
return existingID, false, nil
|
||||
}
|
||||
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
res, err := d.db.Exec(
|
||||
"INSERT INTO config_backups (agent_id, hash, size, config_xml, created_at) VALUES (?, ?, ?, ?, ?)",
|
||||
agentID, hash, size, configXML, now,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, false, fmt.Errorf("Backup speichern: %w", err)
|
||||
}
|
||||
id, _ := res.LastInsertId()
|
||||
return id, true, nil
|
||||
}
|
||||
|
||||
// GetConfigBackups listet alle Backups eines Agents
|
||||
func (d *Database) GetConfigBackups(agentID string, limit int) ([]map[string]interface{}, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
rows, err := d.db.Query(
|
||||
"SELECT id, hash, size, created_at FROM config_backups WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?",
|
||||
agentID, limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var backups []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var hash, createdAt string
|
||||
var size int
|
||||
if err := rows.Scan(&id, &hash, &size, &createdAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backups = append(backups, map[string]interface{}{
|
||||
"id": id,
|
||||
"hash": hash,
|
||||
"size": size,
|
||||
"created_at": createdAt,
|
||||
})
|
||||
}
|
||||
if backups == nil {
|
||||
backups = []map[string]interface{}{}
|
||||
}
|
||||
return backups, rows.Err()
|
||||
}
|
||||
|
||||
// GetConfigBackup holt ein einzelnes Backup inkl. config_xml
|
||||
func (d *Database) GetConfigBackup(agentID string, backupID int64) (map[string]interface{}, error) {
|
||||
var hash, configXML, createdAt string
|
||||
var size int
|
||||
var id int64
|
||||
err := d.db.QueryRow(
|
||||
"SELECT id, hash, size, config_xml, created_at FROM config_backups WHERE agent_id = ? AND id = ?",
|
||||
agentID, backupID,
|
||||
).Scan(&id, &hash, &size, &configXML, &createdAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"id": id,
|
||||
"hash": hash,
|
||||
"size": size,
|
||||
"config_xml": configXML,
|
||||
"created_at": createdAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLatestConfigBackup holt das neueste Backup
|
||||
func (d *Database) GetLatestConfigBackup(agentID string) (map[string]interface{}, error) {
|
||||
var hash, configXML, createdAt string
|
||||
var size int
|
||||
var id int64
|
||||
err := d.db.QueryRow(
|
||||
"SELECT id, hash, size, config_xml, created_at FROM config_backups WHERE agent_id = ? ORDER BY created_at DESC LIMIT 1",
|
||||
agentID,
|
||||
).Scan(&id, &hash, &size, &configXML, &createdAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"id": id,
|
||||
"hash": hash,
|
||||
"size": size,
|
||||
"config_xml": configXML,
|
||||
"created_at": createdAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteConfigBackup loescht ein einzelnes Backup
|
||||
func (d *Database) DeleteConfigBackup(agentID string, backupID int64) (bool, error) {
|
||||
res, err := d.db.Exec("DELETE FROM config_backups WHERE agent_id = ? AND id = ?", agentID, backupID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rows, _ := res.RowsAffected()
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
// DeleteAgent - Agent und zugehoerige Daten loeschen
|
||||
func (d *Database) DeleteAgent(id string) (bool, error) {
|
||||
// Foreign Key Cascade aktivieren
|
||||
|
||||
Reference in New Issue
Block a user