Files
fastestmirror/internal/config/manager.go
2025-09-13 02:47:20 +03:00

219 lines
5.7 KiB
Go

package config
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"git.gostacks.org/iwasforcedtobehere/fastestmirror/internal/distro"
)
// ConfigManager handles backup and modification of distribution config files
type ConfigManager struct {
DistroInfo *distro.DistroInfo
BackupDir string
}
// NewConfigManager creates a new configuration manager
func NewConfigManager(distroInfo *distro.DistroInfo) *ConfigManager {
return &ConfigManager{
DistroInfo: distroInfo,
BackupDir: "/etc/fastestmirror/backups",
}
}
// BackupConfig creates a backup of the current configuration
func (cm *ConfigManager) BackupConfig() (string, error) {
configFile := cm.DistroInfo.GetConfigFile()
if configFile == "" {
return "", fmt.Errorf("no configuration file defined for %s", cm.DistroInfo.Family)
}
// Create backup directory if it doesn't exist
if err := os.MkdirAll(cm.BackupDir, 0755); err != nil {
return "", fmt.Errorf("failed to create backup directory: %w", err)
}
// Generate backup filename with timestamp
timestamp := time.Now().Format("20060102-150405")
backupName := fmt.Sprintf("%s.backup.%s", filepath.Base(configFile), timestamp)
backupPath := filepath.Join(cm.BackupDir, backupName)
// Copy original file to backup location
if err := cm.copyFile(configFile, backupPath); err != nil {
return "", fmt.Errorf("failed to create backup: %w", err)
}
return backupPath, nil
}
// ApplyMirror applies the fastest mirror to the configuration
func (cm *ConfigManager) ApplyMirror(mirrorURL string) error {
switch cm.DistroInfo.Family {
case "debian":
return cm.applyDebianMirror(mirrorURL)
case "arch":
return cm.applyArchMirror(mirrorURL)
default:
return fmt.Errorf("configuration management for %s family not implemented yet", cm.DistroInfo.Family)
}
}
// applyDebianMirror updates /etc/apt/sources.list for Debian-based systems
func (cm *ConfigManager) applyDebianMirror(mirrorURL string) error {
configFile := "/etc/apt/sources.list"
// Read current configuration
file, err := os.Open(configFile)
if err != nil {
return fmt.Errorf("failed to read %s: %w", configFile, err)
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
// Process each line and replace mirror URLs
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == "" || strings.HasPrefix(strings.TrimSpace(line), "#") {
// Keep comments and empty lines as-is
lines = append(lines, line)
continue
}
// Parse and update deb/deb-src lines
if strings.HasPrefix(strings.TrimSpace(line), "deb") {
updatedLine := cm.updateDebianLine(line, mirrorURL)
lines = append(lines, updatedLine)
} else {
lines = append(lines, line)
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading %s: %w", configFile, err)
}
// Write updated configuration
return cm.writeLines(configFile, lines)
}
// updateDebianLine updates a single deb line with the new mirror URL
func (cm *ConfigManager) updateDebianLine(line, newMirrorURL string) string {
parts := strings.Fields(line)
if len(parts) < 3 {
return line // Invalid line, keep as-is
}
// parts[0] = "deb" or "deb-src"
// parts[1] = URL
// parts[2] = suite/codename
// parts[3+] = components
parts[1] = strings.TrimSuffix(newMirrorURL, "/") + "/"
return strings.Join(parts, " ")
}
// applyArchMirror updates /etc/pacman.d/mirrorlist for Arch-based systems
func (cm *ConfigManager) applyArchMirror(mirrorURL string) error {
configFile := "/etc/pacman.d/mirrorlist"
// Read current configuration
file, err := os.Open(configFile)
if err != nil {
return fmt.Errorf("failed to read %s: %w", configFile, err)
}
defer file.Close()
var lines []string
firstMirrorAdded := false
scanner := bufio.NewScanner(file)
// Add the fastest mirror at the top (uncommented)
lines = append(lines, fmt.Sprintf("# FastestMirror: Added %s", time.Now().Format("2006-01-02 15:04:05")))
lines = append(lines, fmt.Sprintf("Server = %s/$repo/os/$arch", strings.TrimSuffix(mirrorURL, "/")))
lines = append(lines, "")
firstMirrorAdded = true
// Process existing lines, commenting out other Server entries
for scanner.Scan() {
line := scanner.Text()
trimmed := strings.TrimSpace(line)
// Comment out existing Server lines (but keep them for reference)
if strings.HasPrefix(trimmed, "Server = ") && firstMirrorAdded {
lines = append(lines, "#"+line)
} else {
lines = append(lines, line)
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading %s: %w", configFile, err)
}
// Write updated configuration
return cm.writeLines(configFile, lines)
}
// copyFile copies a file from src to dst
func (cm *ConfigManager) copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
if err != nil {
return err
}
// Copy file permissions
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
return os.Chmod(dst, srcInfo.Mode())
}
// writeLines writes lines to a file
func (cm *ConfigManager) writeLines(filename string, lines []string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
for _, line := range lines {
if _, err := writer.WriteString(line + "\n"); err != nil {
return err
}
}
return writer.Flush()
}
// RestoreBackup restores a configuration from backup
func (cm *ConfigManager) RestoreBackup(backupPath string) error {
configFile := cm.DistroInfo.GetConfigFile()
if configFile == "" {
return fmt.Errorf("no configuration file defined for %s", cm.DistroInfo.Family)
}
return cm.copyFile(backupPath, configFile)
}