mirror of https://github.com/jetkvm/kvm.git
262 lines
6.0 KiB
Go
262 lines
6.0 KiB
Go
package nmlite
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"golang.org/x/net/idna"
|
|
)
|
|
|
|
const (
|
|
hostnamePath = "/etc/hostname"
|
|
hostsPath = "/etc/hosts"
|
|
)
|
|
|
|
// SetHostname sets the system hostname and updates /etc/hosts
|
|
func (hm *ResolvConfManager) SetHostname(hostname, domain string) error {
|
|
hostname = ToValidHostname(strings.TrimSpace(hostname))
|
|
domain = ToValidHostname(strings.TrimSpace(domain))
|
|
|
|
if hostname == "" {
|
|
return fmt.Errorf("invalid hostname: %s", hostname)
|
|
}
|
|
|
|
hm.hostname = hostname
|
|
hm.domain = domain
|
|
|
|
return hm.reconcileHostname()
|
|
}
|
|
|
|
func (hm *ResolvConfManager) Domain() string {
|
|
hm.mu.Lock()
|
|
defer hm.mu.Unlock()
|
|
return hm.getDomain()
|
|
}
|
|
|
|
func (hm *ResolvConfManager) Hostname() string {
|
|
hm.mu.Lock()
|
|
defer hm.mu.Unlock()
|
|
return hm.getHostname()
|
|
}
|
|
|
|
func (hm *ResolvConfManager) FQDN() string {
|
|
hm.mu.Lock()
|
|
defer hm.mu.Unlock()
|
|
return hm.getFQDN()
|
|
}
|
|
|
|
func (hm *ResolvConfManager) getFQDN() string {
|
|
hostname := hm.getHostname()
|
|
domain := hm.getDomain()
|
|
|
|
if domain == "" {
|
|
return hostname
|
|
}
|
|
|
|
return fmt.Sprintf("%s.%s", hostname, domain)
|
|
}
|
|
|
|
func (hm *ResolvConfManager) getHostname() string {
|
|
if hm.hostname != "" {
|
|
return hm.hostname
|
|
}
|
|
return "jetkvm"
|
|
}
|
|
|
|
func (hm *ResolvConfManager) getDomain() string {
|
|
if hm.domain != "" {
|
|
return hm.domain
|
|
}
|
|
|
|
for _, iface := range hm.conf.ConfigIPv4 {
|
|
if iface.Domain != "" {
|
|
return iface.Domain
|
|
}
|
|
}
|
|
|
|
for _, iface := range hm.conf.ConfigIPv6 {
|
|
if iface.Domain != "" {
|
|
return iface.Domain
|
|
}
|
|
}
|
|
|
|
return "local"
|
|
}
|
|
|
|
func (hm *ResolvConfManager) reconcileHostname() error {
|
|
hm.mu.Lock()
|
|
domain := hm.getDomain()
|
|
hostname := hm.hostname
|
|
if hostname == "" {
|
|
hostname = "jetkvm"
|
|
}
|
|
hm.mu.Unlock()
|
|
|
|
fqdn := hostname
|
|
if fqdn != "" {
|
|
fqdn = fmt.Sprintf("%s.%s", hostname, domain)
|
|
}
|
|
|
|
hm.logger.Info().
|
|
Str("hostname", hostname).
|
|
Str("fqdn", fqdn).
|
|
Msg("setting hostname")
|
|
|
|
// Update /etc/hostname
|
|
if err := hm.updateEtcHostname(hostname); err != nil {
|
|
return fmt.Errorf("failed to update /etc/hostname: %w", err)
|
|
}
|
|
|
|
// Update /etc/hosts
|
|
if err := hm.updateEtcHosts(hostname, fqdn); err != nil {
|
|
return fmt.Errorf("failed to update /etc/hosts: %w", err)
|
|
}
|
|
|
|
// Set the hostname using hostname command
|
|
if err := hm.setSystemHostname(hostname); err != nil {
|
|
return fmt.Errorf("failed to set system hostname: %w", err)
|
|
}
|
|
|
|
hm.logger.Info().
|
|
Str("hostname", hostname).
|
|
Str("fqdn", fqdn).
|
|
Msg("hostname set successfully")
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetCurrentHostname returns the current system hostname
|
|
func (hm *ResolvConfManager) GetCurrentHostname() (string, error) {
|
|
return os.Hostname()
|
|
}
|
|
|
|
// GetCurrentFQDN returns the current FQDN
|
|
func (hm *ResolvConfManager) GetCurrentFQDN() (string, error) {
|
|
hostname, err := hm.GetCurrentHostname()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Try to get the FQDN from /etc/hosts
|
|
return hm.getFQDNFromHosts(hostname)
|
|
}
|
|
|
|
// updateEtcHostname updates the /etc/hostname file
|
|
func (hm *ResolvConfManager) updateEtcHostname(hostname string) error {
|
|
if err := os.WriteFile(hostnamePath, []byte(hostname), 0644); err != nil {
|
|
return fmt.Errorf("failed to write %s: %w", hostnamePath, err)
|
|
}
|
|
|
|
hm.logger.Debug().Str("file", hostnamePath).Str("hostname", hostname).Msg("updated /etc/hostname")
|
|
return nil
|
|
}
|
|
|
|
// updateEtcHosts updates the /etc/hosts file
|
|
func (hm *ResolvConfManager) updateEtcHosts(hostname, fqdn string) error {
|
|
// Open /etc/hosts for reading and writing
|
|
hostsFile, err := os.OpenFile(hostsPath, os.O_RDWR|os.O_SYNC, os.ModeExclusive)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open %s: %w", hostsPath, err)
|
|
}
|
|
defer hostsFile.Close()
|
|
|
|
// Read all lines
|
|
if _, err := hostsFile.Seek(0, io.SeekStart); err != nil {
|
|
return fmt.Errorf("failed to seek %s: %w", hostsPath, err)
|
|
}
|
|
|
|
lines, err := io.ReadAll(hostsFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read %s: %w", hostsPath, err)
|
|
}
|
|
|
|
// Process lines
|
|
newLines := []string{}
|
|
hostLine := fmt.Sprintf("127.0.1.1\t%s %s", hostname, fqdn)
|
|
hostLineExists := false
|
|
|
|
for _, line := range strings.Split(string(lines), "\n") {
|
|
if strings.HasPrefix(line, "127.0.1.1") {
|
|
hostLineExists = true
|
|
line = hostLine
|
|
}
|
|
newLines = append(newLines, line)
|
|
}
|
|
|
|
// Add host line if it doesn't exist
|
|
if !hostLineExists {
|
|
newLines = append(newLines, hostLine)
|
|
}
|
|
|
|
// Write back to file
|
|
if err := hostsFile.Truncate(0); err != nil {
|
|
return fmt.Errorf("failed to truncate %s: %w", hostsPath, err)
|
|
}
|
|
|
|
if _, err := hostsFile.Seek(0, io.SeekStart); err != nil {
|
|
return fmt.Errorf("failed to seek %s: %w", hostsPath, err)
|
|
}
|
|
|
|
if _, err := hostsFile.Write([]byte(strings.Join(newLines, "\n"))); err != nil {
|
|
return fmt.Errorf("failed to write %s: %w", hostsPath, err)
|
|
}
|
|
|
|
hm.logger.Debug().
|
|
Str("file", hostsPath).
|
|
Str("hostname", hostname).
|
|
Str("fqdn", fqdn).
|
|
Msg("updated /etc/hosts")
|
|
|
|
return nil
|
|
}
|
|
|
|
// setSystemHostname sets the system hostname using the hostname command
|
|
func (hm *ResolvConfManager) setSystemHostname(hostname string) error {
|
|
cmd := exec.Command("hostname", "-F", hostnamePath)
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to run hostname command: %w", err)
|
|
}
|
|
|
|
hm.logger.Debug().Str("hostname", hostname).Msg("set system hostname")
|
|
return nil
|
|
}
|
|
|
|
// getFQDNFromHosts tries to get the FQDN from /etc/hosts
|
|
func (hm *ResolvConfManager) getFQDNFromHosts(hostname string) (string, error) {
|
|
content, err := os.ReadFile(hostsPath)
|
|
if err != nil {
|
|
return hostname, nil // Return hostname as fallback
|
|
}
|
|
|
|
lines := strings.Split(string(content), "\n")
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "127.0.1.1") {
|
|
parts := strings.Fields(line)
|
|
if len(parts) >= 2 {
|
|
// The second part should be the FQDN
|
|
return parts[1], nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return hostname, nil // Return hostname as fallback
|
|
}
|
|
|
|
// ToValidHostname converts a hostname to a valid format
|
|
func ToValidHostname(hostname string) string {
|
|
ascii, err := idna.Lookup.ToASCII(hostname)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return ascii
|
|
}
|
|
|
|
// ValidateHostname validates a hostname
|
|
func ValidateHostname(hostname string) error {
|
|
_, err := idna.Lookup.ToASCII(hostname)
|
|
return err
|
|
}
|