219 lines
5.7 KiB
Go
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)
|
|
}
|