Files
kubesolo-os/cloud-init/hostname.go
Adolfo Delorenzo d900fa920e feat: add cloud-init Go parser (Phase 2)
Implement a lightweight cloud-init system for first-boot configuration:
- Go parser for YAML config (hostname, network, KubeSolo settings)
- Static/DHCP network modes with DNS override
- KubeSolo extra flags and API server SAN configuration
- Portainer Edge Agent and air-gapped deployment support
- New init stage 45-cloud-init.sh runs before network/hostname stages
- Stages 50/60 skip gracefully when cloud-init has already applied
- Build script compiles static Linux/amd64 binary (~2.7 MB)
- 17 unit tests covering parsing, validation, and example files
- Full documentation at docs/cloud-init.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 10:39:05 -06:00

81 lines
2.0 KiB
Go

package cloudinit
import (
"fmt"
"log/slog"
"os"
"strings"
)
// ApplyHostname sets the system hostname and updates /etc/hostname and /etc/hosts.
func ApplyHostname(cfg *Config) error {
hostname := cfg.Hostname
if hostname == "" {
slog.Info("no hostname in cloud-init, skipping")
return nil
}
// Set the running hostname
if err := os.WriteFile("/proc/sys/kernel/hostname", []byte(hostname), 0o644); err != nil {
// Fallback: use the hostname command
if err := run("hostname", hostname); err != nil {
return fmt.Errorf("setting hostname: %w", err)
}
}
// Write /etc/hostname
if err := os.WriteFile("/etc/hostname", []byte(hostname+"\n"), 0o644); err != nil {
return fmt.Errorf("writing /etc/hostname: %w", err)
}
// Ensure hostname is in /etc/hosts
if err := ensureHostsEntry(hostname); err != nil {
return fmt.Errorf("updating /etc/hosts: %w", err)
}
slog.Info("hostname set", "hostname", hostname)
return nil
}
// SaveHostname writes the hostname to the persistent data partition so it
// survives reboots (even without cloud-init on next boot).
func SaveHostname(cfg *Config, destDir string) error {
if cfg.Hostname == "" {
return nil
}
if err := os.MkdirAll(destDir, 0o755); err != nil {
return fmt.Errorf("creating hostname dir: %w", err)
}
dest := destDir + "/hostname"
if err := os.WriteFile(dest, []byte(cfg.Hostname+"\n"), 0o644); err != nil {
return fmt.Errorf("writing persistent hostname: %w", err)
}
slog.Info("hostname saved", "path", dest)
return nil
}
func ensureHostsEntry(hostname string) error {
data, err := os.ReadFile("/etc/hosts")
if err != nil && !os.IsNotExist(err) {
return err
}
content := string(data)
entry := "127.0.0.1 " + hostname
// Check if already present
for _, line := range strings.Split(content, "\n") {
if strings.Contains(line, hostname) {
return nil
}
}
// Append
if !strings.HasSuffix(content, "\n") && content != "" {
content += "\n"
}
content += entry + "\n"
return os.WriteFile("/etc/hosts", []byte(content), 0o644)
}