diff --git a/Dockerfile.test b/Dockerfile.test index df118ca..7c41487 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,6 +1,7 @@ ARG BASE_IMAGE=ubuntu:24.04 ARG VALIDATE_IMAGE=ubuntu:24.04 + FROM golang AS build WORKDIR /app COPY go.mod go.sum . @@ -19,9 +20,13 @@ COPY --from=build /app/kairos-init /kairos-init RUN /kairos-init -l debug --validate FROM ${BASE_IMAGE} +ARG VARIANT=core +ARG MODEL=generic +ARG TRUSTED_BOOT=false +ARG K3S_PROVIDER=k3s COPY --from=kairos-init /kairos-init /kairos-init -RUN /kairos-init -l debug -s install -RUN /kairos-init -l debug -s init -RUN /kairos-init -l debug --validate +RUN /kairos-init -l debug -s install -m "${MODEL}" -v "${VARIANT}" -t "${TRUSTED_BOOT}" -k "${K3S_PROVIDER}" +RUN /kairos-init -l debug -s init -m "${MODEL}" -v "${VARIANT}" -t "${TRUSTED_BOOT}" -k "${K3S_PROVIDER}" +RUN /kairos-init -l debug --validate -m "${MODEL}" -v "${VARIANT}" -t "${TRUSTED_BOOT}" -k "${K3S_PROVIDER}" RUN rm /kairos-init \ No newline at end of file diff --git a/main.go b/main.go index ed86450..f116334 100644 --- a/main.go +++ b/main.go @@ -17,10 +17,14 @@ import ( func main() { var trusted string var validate bool + var variant string + var ksProvider string + flag.StringVar(&config.DefaultConfig.Level, "l", "info", "set the log level") flag.StringVar(&config.DefaultConfig.Stage, "s", "all", "set the stage to run") flag.StringVar(&config.DefaultConfig.Model, "m", "generic", "model to build for, like generic or rpi4") - flag.StringVar(&config.DefaultConfig.Variant, "v", "core", "variant to build (core or standard for k3s flavor) (shorthand: -v)") + flag.StringVar(&variant, "v", "core", "variant to build (core or standard for k3s flavor) (shorthand: -v)") + flag.StringVar(&ksProvider, "k", "k3s", "Kubernetes provider (shorthand: -k)") flag.StringVar(&config.DefaultConfig.Registry, "r", "quay.io/kairos", "registry and org where the image is gonna be pushed. This is mainly used on upgrades to search for available images to upgrade to") flag.StringVar(&trusted, "t", "false", "init the system for Trusted Boot, changes bootloader to systemd") flag.StringVar(&config.DefaultConfig.FrameworkVersion, "f", values.GetFrameworkVersion(), "set the framework version to use") @@ -50,11 +54,36 @@ func main() { os.Exit(0) } + if variant == "" { + // Set default variant + config.DefaultConfig.Variant = config.CoreVariant + } else { + // Try to load the variant + err := config.DefaultConfig.Variant.FromString(variant) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + os.Exit(1) + } + } + + if ksProvider == "" { + // Set default variant + config.DefaultConfig.KubernetesProvider = config.K3sProvider + } else { + // Try to load the variant + err := config.DefaultConfig.KubernetesProvider.FromString(ksProvider) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + os.Exit(1) + } + } + logger := types.NewKairosLogger("kairos-init", config.DefaultConfig.Level, false) logger.Infof("Starting kairos-init version %s", values.GetVersion()) logger.Debug(litter.Sdump(values.GetFullVersion())) // Validate flags are being passed with actual values + // We dont care about variant and provider as we are setting that to a default value if value passed is empty requiredFlags := []struct { name string value string @@ -62,7 +91,6 @@ func main() { {"l", config.DefaultConfig.Level}, {"s", config.DefaultConfig.Stage}, {"m", config.DefaultConfig.Model}, - {"v", config.DefaultConfig.Variant}, {"r", config.DefaultConfig.Registry}, {"t", trusted}, {"f", config.DefaultConfig.FrameworkVersion}, diff --git a/pkg/config/config.go b/pkg/config/config.go index 58a5d63..5503ae2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,16 +1,62 @@ package config +import "fmt" + // Config is the struct to track the config of the init image // So we can access it from anywhere type Config struct { - Level string - Stage string - Model string - FrameworkVersion string - Variant string - Registry string - TrustedBoot bool - Fips bool + Level string + Stage string + Model string + FrameworkVersion string + Variant Variant + Registry string + TrustedBoot bool + Fips bool + KubernetesProvider KubernetesProvider + KubernetesVersion string } var DefaultConfig = Config{} + +type Variant string + +func (v Variant) Equal(s string) bool { + return string(v) == s +} + +func (v Variant) String() string { + return string(v) +} + +func (v *Variant) FromString(variant string) error { + *v = Variant(variant) + switch *v { + case CoreVariant, StandardVariant: + return nil + default: + return fmt.Errorf("invalid variant: %s, possible values are %s", variant, ValidVariants) + } +} + +const CoreVariant Variant = "core" +const StandardVariant Variant = "standard" + +var ValidVariants = []Variant{CoreVariant, StandardVariant} + +type KubernetesProvider string + +func (v *KubernetesProvider) FromString(provider string) error { + *v = KubernetesProvider(provider) + switch *v { + case K3sProvider, K0sProvider: + return nil + default: + return fmt.Errorf("invalid Kubernetes provider: %s, possible values are %s", provider, ValidProviders) + } +} + +const K3sProvider KubernetesProvider = "k3s" +const K0sProvider KubernetesProvider = "k0s" + +var ValidProviders = []KubernetesProvider{K3sProvider, K0sProvider} diff --git a/pkg/stages/stages.go b/pkg/stages/stages.go index 913bedd..a267fe1 100644 --- a/pkg/stages/stages.go +++ b/pkg/stages/stages.go @@ -130,7 +130,7 @@ func GetKairosReleaseStage(sis values.System, log types.KairosLogger) []schema.S "KAIROS_FLAVOR_RELEASE": flavorRelease, "KAIROS_FAMILY": sis.Family.String(), "KAIROS_MODEL": config.DefaultConfig.Model, // NEEDED or it breaks boot! - "KAIROS_VARIANT": config.DefaultConfig.Variant, + "KAIROS_VARIANT": config.DefaultConfig.Variant.String(), "KAIROS_REGISTRY_AND_ORG": config.DefaultConfig.Registry, // Needed for upgrades to search for images "KAIROS_BUG_REPORT_URL": "https://github.com/kairos-io/kairos/issues", "KAIROS_HOME_URL": "https://github.com/kairos-io/kairos", @@ -420,6 +420,182 @@ func GetInstallFrameworkStage(_ values.System, _ types.KairosLogger) []schema.St } } +// GetInstallProviderAndKubernetes will install the provider and kubernetes packages +func GetInstallProviderAndKubernetes(_ values.System, _ types.KairosLogger) []schema.Stage { + var data []schema.Stage + + // If its core we dont do anything here + if config.DefaultConfig.Variant.String() == "core" { + return data + } + + data = append(data, []schema.Stage{ + { + Name: "Install Provider packages", + UnpackImages: []schema.UnpackImageConf{ + { + Source: values.GetProviderPackage(), + Target: "/", + }, + }, + }, + }...) + + switch config.DefaultConfig.KubernetesProvider { + case config.K3sProvider: + cmd := "INSTALL_K3S_BIN_DIR=/usr/bin INSTALL_K3S_SKIP_ENABLE=true INSTALL_K3S_SKIP_SELINUX_RPM=true" + // Append version if any, otherwise default to latest + if config.DefaultConfig.KubernetesVersion != "" { + cmd = fmt.Sprintf("INSTALL_K3S_VERSION=v%s %s", config.DefaultConfig.KubernetesVersion, cmd) + } + data = append(data, []schema.Stage{ + { + Name: "Install Kubernetes packages", + Commands: []string{ + "curl -sfL https://get.k3s.io > installer.sh", + "chmod +x installer.sh", + fmt.Sprintf("%s sh installer.sh", cmd), + fmt.Sprintf("%s sh installer.sh agent", cmd), + }, + }, + }...) + case config.K0sProvider: + cmd := "sh installer.sh" + // Append version if any, otherwise default to latest + if config.DefaultConfig.KubernetesVersion != "" { + cmd = fmt.Sprintf("K0S_VERSION=%s %s", config.DefaultConfig.KubernetesVersion, cmd) + } + data = append(data, []schema.Stage{ + { + Name: "Install Kubernetes packages", + Commands: []string{ + "curl -sfL https://get.k0s.sh > installer.sh", + "chmod +x installer.sh", + cmd, + "rm installer.sh", + "mv /usr/local/bin/k0s /usr/bin/k0s", + }, + }, + { + Name: "Create k0s services for systemd", + If: `[ -e "/sbin/systemctl" ] || [ -e "/usr/bin/systemctl" ] || [ -e "/usr/sbin/systemctl" ] || [ -e "/usr/bin/systemctl" ]`, + Files: []schema.File{ + { + Path: "/etc/systemd/system/k0scontroller.service", + Permissions: 0644, + Owner: 0, + Group: 0, + Content: `[Unit] +Description=k0s - Zero Friction Kubernetes +Documentation=https://docs.k0sproject.io +ConditionFileIsExecutable=/usr/bin/k0s + +After=network-online.target +Wants=network-online.target + +[Service] +StartLimitInterval=5 +StartLimitBurst=10 +ExecStart=/usr/bin/k0s controller + +RestartSec=10 +Delegate=yes +KillMode=process +LimitCORE=infinity +TasksMax=infinity +TimeoutStartSec=0 +LimitNOFILE=999999 +Restart=always + +[Install] +WantedBy=multi-user.target`, + }, + { + Path: "/etc/systemd/system/k0sworker.service", + Permissions: 0644, + Owner: 0, + Group: 0, + Content: `[Unit] +Description=k0s - Zero Friction Kubernetes +Documentation=https://docs.k0sproject.io +ConditionFileIsExecutable=/usr/bin/k0s + +After=network-online.target +Wants=network-online.target + +[Service] +StartLimitInterval=5 +StartLimitBurst=10 +ExecStart=/usr/bin/k0s worker + +RestartSec=10 +Delegate=yes +KillMode=process +LimitCORE=infinity +TasksMax=infinity +TimeoutStartSec=0 +LimitNOFILE=999999 +Restart=always + +[Install] +WantedBy=multi-user.target`, + }, + }, + }, + { + Name: "Create k0s services for openrc", + If: `[ -f "/sbin/openrc" ]`, + Files: []schema.File{ + { + Path: "/etc/init.d/k0scontroller", + Permissions: 0755, + Owner: 0, + Group: 0, + Content: `#!/sbin/openrc-run +supervisor=supervise-daemon +description="k0s - Zero Friction Kubernetes" +command=/usr/bin/k0s +command_args="'controller' " +name=$(basename $(readlink -f $command)) +supervise_daemon_args="--stdout /var/log/${name}.log --stderr /var/log/${name}.err" + +: "${rc_ulimit=-n 1048576 -u unlimited}" +depend() { + need cgroups + need net + use dns + after firewall +}`, + }, + { + Path: "/etc/init.d/k0sworker", + Permissions: 0755, + Owner: 0, + Group: 0, + Content: `#!/sbin/openrc-run +supervisor=supervise-daemon +description="k0s - Zero Friction Kubernetes" +command=/usr/bin/k0s +command_args="'worker' " +name=$(basename $(readlink -f $command)) +supervise_daemon_args="--stdout /var/log/${name}.log --stderr /var/log/${name}.err" + +: "${rc_ulimit=-n 1048576 -u unlimited}" +depend() { + need cgroups + need net + use dns + after firewall +}`, + }, + }, + }, + }...) + } + + return data +} + func GetServicesStage(_ values.System, _ types.KairosLogger) []schema.Stage { return []schema.Stage{ { @@ -474,25 +650,6 @@ func GetServicesStage(_ values.System, _ types.KairosLogger) []schema.Stage { } } -// GetInstallStandardStage Returns the standard install stage so it installs the standard packages like k3s and provider -// Not used for now -func GetInstallStandardStage(sis values.System, logger types.KairosLogger) []schema.Stage { - var data []schema.Stage - /* - if config.DefaultConfig.Variant == "standard" { - data = append(data, schema.Stage{ - Name: "Install standard packages", - Commands: []string{ - "luet install -y k9s-openrc/systemd/k3s", - "luet install -y provider-kairos", - }, - }) - } - */ - - return data -} - // RunAllStages Runs all the stages in the correct order func RunAllStages(logger types.KairosLogger) (schema.YipConfig, error) { fullYipConfig := schema.YipConfig{Stages: map[string][]schema.Stage{}} @@ -535,7 +692,7 @@ func RunInstallStage(logger types.KairosLogger) (schema.YipConfig, error) { data.Stages["install"] = installStage // Add the framework stage data.Stages["install"] = append(data.Stages["install"], GetInstallFrameworkStage(sis, logger)...) - data.Stages["install"] = append(data.Stages["install"], GetInstallStandardStage(sis, logger)...) + data.Stages["install"] = append(data.Stages["install"], GetInstallProviderAndKubernetes(sis, logger)...) // Run things after we install packages and framework data.Stages["after-install"] = []schema.Stage{} diff --git a/pkg/validation/validate.go b/pkg/validation/validate.go index 9af915b..d526330 100644 --- a/pkg/validation/validate.go +++ b/pkg/validation/validate.go @@ -35,12 +35,15 @@ func (v *Validator) Validate() error { "kcrypt-discovery-challenger", } - // Not yet as we dont install the k3s stuff ourselves - /* - if config.DefaultConfig.Variant == "standard" { - binaries = append(binaries, "k3s", "agent-provider-kairos", "kairos") + if config.DefaultConfig.Variant == "standard" { + binaries = append(binaries, "agent-provider-kairos", "kairos") + if config.DefaultConfig.KubernetesProvider == config.K3sProvider { + binaries = append(binaries, "k3s") + } + if config.DefaultConfig.KubernetesProvider == config.K0sProvider { + binaries = append(binaries, "k0s") } - */ + } // Alter path to include our providers path originalPath := os.Getenv("PATH") @@ -89,6 +92,15 @@ func (v *Validator) Validate() error { "kairos-webui", "kairos", } + + if config.DefaultConfig.Variant == "standard" { + switch config.DefaultConfig.KubernetesProvider { + case config.K3sProvider: + services = append(services, "k3s", "k3s-agent") + case config.K0sProvider: + services = append(services, "k0scontroller", "k0sworker") + } + } for _, service := range services { _, err := os.Stat(fmt.Sprintf("/etc/systemd/system/%s.service", service)) if err != nil { diff --git a/pkg/values/versions.go b/pkg/values/versions.go index 056b555..615f4c9 100644 --- a/pkg/values/versions.go +++ b/pkg/values/versions.go @@ -17,6 +17,10 @@ func GetFrameworkVersion() string { return frameWorkVersion } +func GetProviderPackage() string { + return providerPackage +} + func GetVersion() string { return version }