From b5b2a5193468e474ec09ba596c80ff117587714e Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:50:14 +0100 Subject: [PATCH 1/4] feat: add ACME certificate configuration support --- cli/cmd/init_install_config.go | 53 +++++++++++ cli/cmd/update_install_config.go | 92 ++++++++++++++++++- .../installer/config_generator_collector.go | 67 ++++++++++++++ internal/installer/config_manager.go | 10 ++ internal/installer/crypto.go | 14 +++ internal/installer/files/config_yaml.go | 62 ++++++++++++- 6 files changed, 296 insertions(+), 2 deletions(-) diff --git a/cli/cmd/init_install_config.go b/cli/cmd/init_install_config.go index a0c36d49..cd09bb32 100644 --- a/cli/cmd/init_install_config.go +++ b/cli/cmd/init_install_config.go @@ -4,6 +4,7 @@ package cmd import ( + "encoding/json" "fmt" "strings" @@ -67,6 +68,14 @@ type InitInstallConfigOpts struct { MetalLBEnabled bool MetalLBPools []files.MetalLBPool + ACMEEnabled bool + ACMEIssuerName string + ACMEEmail string + ACMEServer string + ACMEEABKeyID string + ACMEEABMacKey string + ACMEDNS01Provider string + CodesphereDomain string CodespherePublicIP string CodesphereWorkspaceHostingBaseDomain string @@ -135,6 +144,14 @@ func AddInitInstallConfigCmd(init *cobra.Command, opts *GlobalOptions) { c.cmd.Flags().BoolVar(&c.Opts.KubernetesManagedByCodesphere, "k8s-managed", true, "Use Codesphere-managed Kubernetes") c.cmd.Flags().StringSliceVar(&c.Opts.KubernetesControlPlanes, "k8s-control-plane", []string{}, "K8s control plane IPs (comma-separated)") + c.cmd.Flags().BoolVar(&c.Opts.ACMEEnabled, "acme-enabled", false, "Enable ACME certificate issuer") + c.cmd.Flags().StringVar(&c.Opts.ACMEIssuerName, "acme-issuer-name", "acme-issuer", "Name for the ACME ClusterIssuer") + c.cmd.Flags().StringVar(&c.Opts.ACMEEmail, "acme-email", "", "Email address for ACME account registration") + c.cmd.Flags().StringVar(&c.Opts.ACMEServer, "acme-server", "https://acme-v02.api.letsencrypt.org/directory", "ACME server URL") + c.cmd.Flags().StringVar(&c.Opts.ACMEEABKeyID, "acme-eab-key-id", "", "External Account Binding key ID (required by some ACME providers)") + c.cmd.Flags().StringVar(&c.Opts.ACMEEABMacKey, "acme-eab-mac-key", "", "External Account Binding MAC key (required by some ACME providers)") + c.cmd.Flags().StringVar(&c.Opts.ACMEDNS01Provider, "acme-dns01-provider", "", "DNS provider for DNS-01 solver (e.g., cloudflare)") + c.cmd.Flags().StringVar(&c.Opts.CodesphereDomain, "domain", "", "Main Codesphere domain") util.MarkFlagRequired(c.cmd, "config") @@ -373,6 +390,38 @@ func (c *InitInstallConfigCmd) updateConfigFromOpts(config *files.RootConfig) *f } } + // ACME configuration + if c.Opts.ACMEEnabled { + if config.Cluster.Certificates.ACME == nil { + config.Cluster.Certificates.ACME = &files.ACMEConfig{} + } + config.Cluster.Certificates.ACME.Enabled = true + + if c.Opts.ACMEIssuerName != "" { + config.Cluster.Certificates.ACME.Name = c.Opts.ACMEIssuerName + } + if c.Opts.ACMEEmail != "" { + config.Cluster.Certificates.ACME.Email = c.Opts.ACMEEmail + } + if c.Opts.ACMEServer != "" { + config.Cluster.Certificates.ACME.Server = c.Opts.ACMEServer + } + + if c.Opts.ACMEEABKeyID != "" { + config.Cluster.Certificates.ACME.EABKeyID = c.Opts.ACMEEABKeyID + } + if c.Opts.ACMEEABMacKey != "" { + config.Cluster.Certificates.ACME.EABMacKey = c.Opts.ACMEEABMacKey + } + + // Configure DNS-01 solver + if c.Opts.ACMEDNS01Provider != "" { + config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{ + Provider: c.Opts.ACMEDNS01Provider, + } + } + } + // Codesphere settings if c.Opts.CodesphereDomain != "" { config.Codesphere.Domain = c.Opts.CodesphereDomain @@ -429,3 +478,7 @@ func (c *InitInstallConfigCmd) updateConfigFromOpts(config *files.RootConfig) *f return config } + +func parseJSON(jsonStr string, target interface{}) error { + return json.Unmarshal([]byte(jsonStr), target) +} diff --git a/cli/cmd/update_install_config.go b/cli/cmd/update_install_config.go index 9cf6bc95..2407c386 100644 --- a/cli/cmd/update_install_config.go +++ b/cli/cmd/update_install_config.go @@ -46,6 +46,14 @@ type UpdateInstallConfigOpts struct { ClusterPublicGatewayServiceType string ClusterPublicGatewayIPAddresses []string + ACMEEnabled bool + ACMEIssuerName string + ACMEEmail string + ACMEServer string + ACMEEABKeyID string + ACMEEABMacKey string + ACMEDNS01Provider string + CodesphereDomain string CodespherePublicIP string CodesphereWorkspaceHostingBaseDomain string @@ -109,6 +117,15 @@ func AddUpdateInstallConfigCmd(update *cobra.Command, opts *GlobalOptions) { c.cmd.Flags().StringVar(&c.Opts.ClusterPublicGatewayServiceType, "cluster-public-gateway-service-type", "", "Cluster public gateway service type") c.cmd.Flags().StringSliceVar(&c.Opts.ClusterPublicGatewayIPAddresses, "cluster-public-gateway-ips", []string{}, "Cluster public gateway IP addresses (comma-separated)") + // ACME update flags + c.cmd.Flags().BoolVar(&c.Opts.ACMEEnabled, "acme-enabled", false, "Enable ACME certificate issuer") + c.cmd.Flags().StringVar(&c.Opts.ACMEIssuerName, "acme-issuer-name", "", "Name for the ACME ClusterIssuer") + c.cmd.Flags().StringVar(&c.Opts.ACMEEmail, "acme-email", "", "Email address for ACME account registration") + c.cmd.Flags().StringVar(&c.Opts.ACMEServer, "acme-server", "", "ACME server URL") + c.cmd.Flags().StringVar(&c.Opts.ACMEEABKeyID, "acme-eab-key-id", "", "External Account Binding key ID (required by some ACME providers)") + c.cmd.Flags().StringVar(&c.Opts.ACMEEABMacKey, "acme-eab-mac-key", "", "External Account Binding MAC key (required by some ACME providers)") + c.cmd.Flags().StringVar(&c.Opts.ACMEDNS01Provider, "acme-dns01-provider", "", "DNS provider for DNS-01 solver") + // Codesphere update flags c.cmd.Flags().StringVar(&c.Opts.CodesphereDomain, "domain", "", "Main Codesphere domain") c.cmd.Flags().StringVar(&c.Opts.CodespherePublicIP, "public-ip", "", "Codesphere public IP address") @@ -254,6 +271,67 @@ func (c *UpdateInstallConfigCmd) applyUpdates(config *files.RootConfig, tracker config.Cluster.PublicGateway.IPAddresses = c.Opts.ClusterPublicGatewayIPAddresses } + // ACME updates + acmeChanged := false + if c.Opts.ACMEEnabled { + if config.Cluster.Certificates.ACME == nil { + config.Cluster.Certificates.ACME = &files.ACMEConfig{} + } + + if !config.Cluster.Certificates.ACME.Enabled { + fmt.Printf("Enabling ACME certificate issuer\n") + config.Cluster.Certificates.ACME.Enabled = true + acmeChanged = true + } + + if c.Opts.ACMEIssuerName != "" && config.Cluster.Certificates.ACME.Name != c.Opts.ACMEIssuerName { + fmt.Printf("Updating ACME issuer name: %s -> %s\n", config.Cluster.Certificates.ACME.Name, c.Opts.ACMEIssuerName) + config.Cluster.Certificates.ACME.Name = c.Opts.ACMEIssuerName + acmeChanged = true + } + + if c.Opts.ACMEEmail != "" && config.Cluster.Certificates.ACME.Email != c.Opts.ACMEEmail { + fmt.Printf("Updating ACME email: %s -> %s\n", config.Cluster.Certificates.ACME.Email, c.Opts.ACMEEmail) + config.Cluster.Certificates.ACME.Email = c.Opts.ACMEEmail + acmeChanged = true + } + + if c.Opts.ACMEServer != "" && config.Cluster.Certificates.ACME.Server != c.Opts.ACMEServer { + fmt.Printf("Updating ACME server: %s -> %s\n", config.Cluster.Certificates.ACME.Server, c.Opts.ACMEServer) + config.Cluster.Certificates.ACME.Server = c.Opts.ACMEServer + acmeChanged = true + } + + if c.Opts.ACMEEABKeyID != "" && config.Cluster.Certificates.ACME.EABKeyID != c.Opts.ACMEEABKeyID { + fmt.Printf("Updating ACME EAB key ID: %s -> %s\n", config.Cluster.Certificates.ACME.EABKeyID, c.Opts.ACMEEABKeyID) + config.Cluster.Certificates.ACME.EABKeyID = c.Opts.ACMEEABKeyID + acmeChanged = true + } + + if c.Opts.ACMEEABMacKey != "" && config.Cluster.Certificates.ACME.EABMacKey != c.Opts.ACMEEABMacKey { + fmt.Printf("Updating ACME EAB MAC key\n") + config.Cluster.Certificates.ACME.EABMacKey = c.Opts.ACMEEABMacKey + acmeChanged = true + } + + // Update DNS-01 solver configuration + if c.Opts.ACMEDNS01Provider != "" { + if config.Cluster.Certificates.ACME.Solver.DNS01 == nil { + config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{} + } + if config.Cluster.Certificates.ACME.Solver.DNS01.Provider != c.Opts.ACMEDNS01Provider { + fmt.Printf("Updating ACME DNS-01 provider: %s -> %s\n", + config.Cluster.Certificates.ACME.Solver.DNS01.Provider, c.Opts.ACMEDNS01Provider) + config.Cluster.Certificates.ACME.Solver.DNS01.Provider = c.Opts.ACMEDNS01Provider + acmeChanged = true + } + } + + if acmeChanged { + tracker.MarkACMEConfigChanged() + } + } + // Codesphere updates if c.Opts.CodesphereDomain != "" && config.Codesphere.Domain != c.Opts.CodesphereDomain { fmt.Printf("Updating Codesphere domain: %s -> %s\n", config.Codesphere.Domain, c.Opts.CodesphereDomain) @@ -326,6 +404,9 @@ func (c *UpdateInstallConfigCmd) printSuccessMessage(tracker *SecretDependencyTr if tracker.NeedsPostgresReplicaCertRegen() { fmt.Println(" ✓ PostgreSQL replica server certificate") } + if tracker.ACMEConfigChanged() { + fmt.Println(" ✓ ACME configuration updated") + } } fmt.Println("\nIMPORTANT: The vault file has been updated with new secrets.") @@ -336,6 +417,7 @@ func (c *UpdateInstallConfigCmd) printSuccessMessage(tracker *SecretDependencyTr type SecretDependencyTracker struct { postgresPrimaryCertNeedsRegen bool postgresReplicaCertNeedsRegen bool + acmeConfigChanged bool } func NewSecretDependencyTracker() *SecretDependencyTracker { @@ -350,6 +432,10 @@ func (t *SecretDependencyTracker) MarkPostgresReplicaCertNeedsRegen() { t.postgresReplicaCertNeedsRegen = true } +func (t *SecretDependencyTracker) MarkACMEConfigChanged() { + t.acmeConfigChanged = true +} + func (t *SecretDependencyTracker) NeedsPostgresPrimaryCertRegen() bool { return t.postgresPrimaryCertNeedsRegen } @@ -358,6 +444,10 @@ func (t *SecretDependencyTracker) NeedsPostgresReplicaCertRegen() bool { return t.postgresReplicaCertNeedsRegen } +func (t *SecretDependencyTracker) ACMEConfigChanged() bool { + return t.acmeConfigChanged +} + func (t *SecretDependencyTracker) HasChanges() bool { - return t.postgresPrimaryCertNeedsRegen || t.postgresReplicaCertNeedsRegen + return t.postgresPrimaryCertNeedsRegen || t.postgresReplicaCertNeedsRegen || t.acmeConfigChanged } diff --git a/internal/installer/config_generator_collector.go b/internal/installer/config_generator_collector.go index 74dfc6de..5082467f 100644 --- a/internal/installer/config_generator_collector.go +++ b/internal/installer/config_generator_collector.go @@ -19,6 +19,7 @@ func (g *InstallConfig) CollectInteractively() error { g.collectK8sConfig(prompter) g.collectGatewayConfig(prompter) g.collectMetalLBConfig(prompter) + g.collectACMEConfig(prompter) g.collectCodesphereConfig(prompter) return nil @@ -210,6 +211,72 @@ func (g *InstallConfig) collectMetalLBConfig(prompter *Prompter) { } } +func (g *InstallConfig) collectACMEConfig(prompter *Prompter) { + fmt.Println("\n=== ACME Certificate Configuration (Optional) ===") + + // Initialize ACME config if it doesn't exist + if g.Config.Cluster.Certificates.ACME == nil { + g.Config.Cluster.Certificates.ACME = &files.ACMEConfig{} + } + + g.Config.Cluster.Certificates.ACME.Enabled = prompter.Bool("Enable ACME certificate issuer (e.g., Let's Encrypt)", g.Config.Cluster.Certificates.ACME.Enabled) + + if g.Config.Cluster.Certificates.ACME.Enabled { + defaultIssuerName := g.Config.Cluster.Certificates.ACME.Name + if defaultIssuerName == "" { + defaultIssuerName = "acme-issuer" + } + g.Config.Cluster.Certificates.ACME.Name = g.collectString(prompter, "ACME issuer name", defaultIssuerName) + + defaultEmail := g.Config.Cluster.Certificates.ACME.Email + if defaultEmail == "" { + defaultEmail = "admin@example.com" + } + g.Config.Cluster.Certificates.ACME.Email = g.collectString(prompter, "Email address for ACME account registration", defaultEmail) + + defaultServer := g.Config.Cluster.Certificates.ACME.Server + if defaultServer == "" { + defaultServer = "https://acme-v02.api.letsencrypt.org/directory" + } + g.Config.Cluster.Certificates.ACME.Server = g.collectString(prompter, "ACME server URL", defaultServer) + + // External Account Binding (EAB) + fmt.Println("\n--- External Account Binding (Optional) ---") + hasEAB := prompter.Bool("Configure External Account Binding (required by some ACME CAs)", g.Config.Cluster.Certificates.ACME.EABKeyID != "") + if hasEAB { + g.Config.Cluster.Certificates.ACME.EABKeyID = g.collectString(prompter, "EAB Key ID", g.Config.Cluster.Certificates.ACME.EABKeyID) + g.Config.Cluster.Certificates.ACME.EABMacKey = g.collectString(prompter, "EAB MAC Key", g.Config.Cluster.Certificates.ACME.EABMacKey) + } else { + g.Config.Cluster.Certificates.ACME.EABKeyID = "" + g.Config.Cluster.Certificates.ACME.EABMacKey = "" + } + + // DNS-01 Challenge Configuration + fmt.Println("\n--- DNS-01 Challenge Configuration (Optional) ---") + if g.Config.Cluster.Certificates.ACME.Solver.DNS01 == nil { + g.Config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{} + } + + useDNS01 := prompter.Bool("Configure DNS-01 challenge solver", g.Config.Cluster.Certificates.ACME.Solver.DNS01.Provider != "") + if useDNS01 { + providerOptions := []string{"route53", "cloudflare", "azure", "gcp", "other"} + defaultProvider := g.Config.Cluster.Certificates.ACME.Solver.DNS01.Provider + if defaultProvider == "" { + defaultProvider = "cloudflare" + } + g.Config.Cluster.Certificates.ACME.Solver.DNS01.Provider = g.collectChoice(prompter, "DNS provider", providerOptions, defaultProvider) + + fmt.Println("Note: Additional DNS provider configuration will need to be added to the vault file.") + fmt.Println("Provider config and secrets should be added manually after generation.") + } else { + g.Config.Cluster.Certificates.ACME.Solver.DNS01 = nil + } + } else { + // If ACME is disabled, clear the config + g.Config.Cluster.Certificates.ACME = nil + } +} + func (g *InstallConfig) collectCodesphereConfig(prompter *Prompter) { fmt.Println("\n=== Codesphere Application Configuration ===") defaultDomain := g.Config.Codesphere.Domain diff --git a/internal/installer/config_manager.go b/internal/installer/config_manager.go index 7d2a4ed5..761dc085 100644 --- a/internal/installer/config_manager.go +++ b/internal/installer/config_manager.go @@ -347,5 +347,15 @@ func (g *InstallConfig) MergeVaultIntoConfig() error { g.Config.Codesphere.GitProviders.GitHub.OAuth.ClientSecret = secret.Fields.Password } + // ACME secrets + if g.Config.Cluster.Certificates.ACME != nil { + if secret, ok := secretsMap["acmeEabKeyId"]; ok && secret.Fields != nil { + g.Config.Cluster.Certificates.ACME.EABKeyID = secret.Fields.Password + } + if secret, ok := secretsMap["acmeEabMacKey"]; ok && secret.Fields != nil { + g.Config.Cluster.Certificates.ACME.EABMacKey = secret.Fields.Password + } + } + return nil } diff --git a/internal/installer/crypto.go b/internal/installer/crypto.go index 0fbe829f..11ac6439 100644 --- a/internal/installer/crypto.go +++ b/internal/installer/crypto.go @@ -158,6 +158,20 @@ func GeneratePassword(length int) string { return base64.StdEncoding.EncodeToString(bytes)[:length] } +func GenerateRSAPrivateKey(bits int) (string, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return "", fmt.Errorf("failed to generate RSA private key: %w", err) + } + + privKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + return string(privKeyPEM), nil +} + func parseCAKeyAndCert(caKeyPEM, caCertPEM string) (*rsa.PrivateKey, *x509.Certificate, error) { caKeyBlock, _ := pem.Decode([]byte(caKeyPEM)) if caKeyBlock == nil { diff --git a/internal/installer/files/config_yaml.go b/internal/installer/files/config_yaml.go index 7b6f1a71..0a43db66 100644 --- a/internal/installer/files/config_yaml.go +++ b/internal/installer/files/config_yaml.go @@ -173,7 +173,8 @@ type ClusterConfig struct { } type ClusterCertificates struct { - CA CAConfig `yaml:"ca"` + CA CAConfig `yaml:"ca"` + ACME *ACMEConfig `yaml:"acme,omitempty"` } type CAConfig struct { @@ -182,6 +183,29 @@ type CAConfig struct { CertPem string `yaml:"certPem"` } +type ACMEConfig struct { + Enabled bool `yaml:"enabled"` + Name string `yaml:"name,omitempty"` + Email string `yaml:"email,omitempty"` + Server string `yaml:"server,omitempty"` + PrivateKeySecretName string `yaml:"-"` + Solver ACMESolver `yaml:"solver"` + + EABKeyID string `yaml:"-"` + EABMacKey string `yaml:"-"` +} + +type ACMESolver struct { + DNS01 *ACMEDNS01Solver `yaml:"dns01,omitempty"` +} + +type ACMEDNS01Solver struct { + Provider string `yaml:"provider"` + Config map[string]interface{} `yaml:"config,omitempty"` + + Secrets map[string]string `yaml:"-"` +} + type GatewayConfig struct { ServiceType string `yaml:"serviceType"` Annotations map[string]string `yaml:"annotations,omitempty"` @@ -432,6 +456,7 @@ func (c *RootConfig) ExtractVault() *InstallVault { c.addCodesphereSecrets(vault) c.addIngressCASecret(vault) + c.addACMESecrets(vault) c.addCephSecrets(vault) c.addPostgresSecrets(vault) c.addManagedServiceSecrets(vault) @@ -494,6 +519,41 @@ func (c *RootConfig) addIngressCASecret(vault *InstallVault) { } } +func (c *RootConfig) addACMESecrets(vault *InstallVault) { + if c.Cluster.Certificates.ACME == nil || !c.Cluster.Certificates.ACME.Enabled { + return + } + + if c.Cluster.Certificates.ACME.EABKeyID != "" { + vault.Secrets = append(vault.Secrets, SecretEntry{ + Name: "acmeEabKeyId", + Fields: &SecretFields{ + Password: c.Cluster.Certificates.ACME.EABKeyID, + }, + }) + } + + if c.Cluster.Certificates.ACME.EABMacKey != "" { + vault.Secrets = append(vault.Secrets, SecretEntry{ + Name: "acmeEabMacKey", + Fields: &SecretFields{ + Password: c.Cluster.Certificates.ACME.EABMacKey, + }, + }) + } + + if c.Cluster.Certificates.ACME.Solver.DNS01 != nil { + for key, value := range c.Cluster.Certificates.ACME.Solver.DNS01.Secrets { + vault.Secrets = append(vault.Secrets, SecretEntry{ + Name: fmt.Sprintf("acmeDNS01%s", Capitalize(key)), + Fields: &SecretFields{ + Password: value, + }, + }) + } + } +} + func (c *RootConfig) addCephSecrets(vault *InstallVault) { if c.Ceph.SshPrivateKey != "" { vault.Secrets = append(vault.Secrets, SecretEntry{ From 48a74bc810e016e5440bd7006d1f5e420392a2a5 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:12:59 +0100 Subject: [PATCH 2/4] ref: remove unused code --- cli/cmd/init_install_config.go | 5 ----- internal/installer/crypto.go | 14 -------------- 2 files changed, 19 deletions(-) diff --git a/cli/cmd/init_install_config.go b/cli/cmd/init_install_config.go index cd09bb32..839c3ed5 100644 --- a/cli/cmd/init_install_config.go +++ b/cli/cmd/init_install_config.go @@ -4,7 +4,6 @@ package cmd import ( - "encoding/json" "fmt" "strings" @@ -478,7 +477,3 @@ func (c *InitInstallConfigCmd) updateConfigFromOpts(config *files.RootConfig) *f return config } - -func parseJSON(jsonStr string, target interface{}) error { - return json.Unmarshal([]byte(jsonStr), target) -} diff --git a/internal/installer/crypto.go b/internal/installer/crypto.go index 11ac6439..0fbe829f 100644 --- a/internal/installer/crypto.go +++ b/internal/installer/crypto.go @@ -158,20 +158,6 @@ func GeneratePassword(length int) string { return base64.StdEncoding.EncodeToString(bytes)[:length] } -func GenerateRSAPrivateKey(bits int) (string, error) { - privateKey, err := rsa.GenerateKey(rand.Reader, bits) - if err != nil { - return "", fmt.Errorf("failed to generate RSA private key: %w", err) - } - - privKeyPEM := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) - - return string(privKeyPEM), nil -} - func parseCAKeyAndCert(caKeyPEM, caCertPEM string) (*rsa.PrivateKey, *x509.Certificate, error) { caKeyBlock, _ := pem.Decode([]byte(caKeyPEM)) if caKeyBlock == nil { From a47c6a36522f2514f21dfcd487b28183721f63e6 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:14:52 +0000 Subject: [PATCH 3/4] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- docs/oms-cli_init_install-config.md | 7 +++++++ docs/oms-cli_update_install-config.md | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/docs/oms-cli_init_install-config.md b/docs/oms-cli_init_install-config.md index ce7c28d0..92d0e3a1 100644 --- a/docs/oms-cli_init_install-config.md +++ b/docs/oms-cli_init_install-config.md @@ -42,6 +42,13 @@ $ oms-cli init install-config --validate -c config.yaml --vault prod.vault.yaml ### Options ``` + --acme-dns01-provider string DNS provider for DNS-01 solver (e.g., cloudflare) + --acme-eab-key-id string External Account Binding key ID (required by some ACME providers) + --acme-eab-mac-key string External Account Binding MAC key (required by some ACME providers) + --acme-email string Email address for ACME account registration + --acme-enabled Enable ACME certificate issuer + --acme-issuer-name string Name for the ACME ClusterIssuer (default "acme-issuer") + --acme-server string ACME server URL (default "https://acme-v02.api.letsencrypt.org/directory") -c, --config string Output file path for config.yaml (default "config.yaml") --dc-id int Datacenter ID --dc-name string Datacenter name diff --git a/docs/oms-cli_update_install-config.md b/docs/oms-cli_update_install-config.md index c215d6a2..512eedd2 100644 --- a/docs/oms-cli_update_install-config.md +++ b/docs/oms-cli_update_install-config.md @@ -35,6 +35,13 @@ $ oms-cli update install-config --k8s-api-server 10.0.0.10 --config config.yaml ### Options ``` + --acme-dns01-provider string DNS provider for DNS-01 solver + --acme-eab-key-id string External Account Binding key ID (required by some ACME providers) + --acme-eab-mac-key string External Account Binding MAC key (required by some ACME providers) + --acme-email string Email address for ACME account registration + --acme-enabled Enable ACME certificate issuer + --acme-issuer-name string Name for the ACME ClusterIssuer + --acme-server string ACME server URL --ceph-nodes-subnet string Ceph nodes subnet --cluster-gateway-ips strings Cluster gateway IP addresses (comma-separated) --cluster-gateway-service-type string Cluster gateway service type From 2cae1f0ea9d7c4f44aaa543d0c1c556553e76681 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:12:44 +0100 Subject: [PATCH 4/4] ref: fmt to log --- cli/cmd/update_install_config.go | 17 +++++++++-------- .../installer/config_generator_collector.go | 11 ++++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cli/cmd/update_install_config.go b/cli/cmd/update_install_config.go index 2407c386..7a60d249 100644 --- a/cli/cmd/update_install_config.go +++ b/cli/cmd/update_install_config.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "log" "strings" csio "github.com/codesphere-cloud/cs-go/pkg/io" @@ -279,37 +280,37 @@ func (c *UpdateInstallConfigCmd) applyUpdates(config *files.RootConfig, tracker } if !config.Cluster.Certificates.ACME.Enabled { - fmt.Printf("Enabling ACME certificate issuer\n") + log.Printf("Enabling ACME certificate issuer\n") config.Cluster.Certificates.ACME.Enabled = true acmeChanged = true } if c.Opts.ACMEIssuerName != "" && config.Cluster.Certificates.ACME.Name != c.Opts.ACMEIssuerName { - fmt.Printf("Updating ACME issuer name: %s -> %s\n", config.Cluster.Certificates.ACME.Name, c.Opts.ACMEIssuerName) + log.Printf("Updating ACME issuer name: %s -> %s\n", config.Cluster.Certificates.ACME.Name, c.Opts.ACMEIssuerName) config.Cluster.Certificates.ACME.Name = c.Opts.ACMEIssuerName acmeChanged = true } if c.Opts.ACMEEmail != "" && config.Cluster.Certificates.ACME.Email != c.Opts.ACMEEmail { - fmt.Printf("Updating ACME email: %s -> %s\n", config.Cluster.Certificates.ACME.Email, c.Opts.ACMEEmail) + log.Printf("Updating ACME email: %s -> %s\n", config.Cluster.Certificates.ACME.Email, c.Opts.ACMEEmail) config.Cluster.Certificates.ACME.Email = c.Opts.ACMEEmail acmeChanged = true } if c.Opts.ACMEServer != "" && config.Cluster.Certificates.ACME.Server != c.Opts.ACMEServer { - fmt.Printf("Updating ACME server: %s -> %s\n", config.Cluster.Certificates.ACME.Server, c.Opts.ACMEServer) + log.Printf("Updating ACME server: %s -> %s\n", config.Cluster.Certificates.ACME.Server, c.Opts.ACMEServer) config.Cluster.Certificates.ACME.Server = c.Opts.ACMEServer acmeChanged = true } if c.Opts.ACMEEABKeyID != "" && config.Cluster.Certificates.ACME.EABKeyID != c.Opts.ACMEEABKeyID { - fmt.Printf("Updating ACME EAB key ID: %s -> %s\n", config.Cluster.Certificates.ACME.EABKeyID, c.Opts.ACMEEABKeyID) + log.Printf("Updating ACME EAB key ID: %s -> %s\n", config.Cluster.Certificates.ACME.EABKeyID, c.Opts.ACMEEABKeyID) config.Cluster.Certificates.ACME.EABKeyID = c.Opts.ACMEEABKeyID acmeChanged = true } if c.Opts.ACMEEABMacKey != "" && config.Cluster.Certificates.ACME.EABMacKey != c.Opts.ACMEEABMacKey { - fmt.Printf("Updating ACME EAB MAC key\n") + log.Printf("Updating ACME EAB MAC key\n") config.Cluster.Certificates.ACME.EABMacKey = c.Opts.ACMEEABMacKey acmeChanged = true } @@ -320,7 +321,7 @@ func (c *UpdateInstallConfigCmd) applyUpdates(config *files.RootConfig, tracker config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{} } if config.Cluster.Certificates.ACME.Solver.DNS01.Provider != c.Opts.ACMEDNS01Provider { - fmt.Printf("Updating ACME DNS-01 provider: %s -> %s\n", + log.Printf("Updating ACME DNS-01 provider: %s -> %s\n", config.Cluster.Certificates.ACME.Solver.DNS01.Provider, c.Opts.ACMEDNS01Provider) config.Cluster.Certificates.ACME.Solver.DNS01.Provider = c.Opts.ACMEDNS01Provider acmeChanged = true @@ -405,7 +406,7 @@ func (c *UpdateInstallConfigCmd) printSuccessMessage(tracker *SecretDependencyTr fmt.Println(" ✓ PostgreSQL replica server certificate") } if tracker.ACMEConfigChanged() { - fmt.Println(" ✓ ACME configuration updated") + log.Println(" ✓ ACME configuration updated") } } diff --git a/internal/installer/config_generator_collector.go b/internal/installer/config_generator_collector.go index 5082467f..1d33fe73 100644 --- a/internal/installer/config_generator_collector.go +++ b/internal/installer/config_generator_collector.go @@ -5,6 +5,7 @@ package installer import ( "fmt" + "log" "github.com/codesphere-cloud/oms/internal/installer/files" ) @@ -212,7 +213,7 @@ func (g *InstallConfig) collectMetalLBConfig(prompter *Prompter) { } func (g *InstallConfig) collectACMEConfig(prompter *Prompter) { - fmt.Println("\n=== ACME Certificate Configuration (Optional) ===") + log.Println("\n=== ACME Certificate Configuration (Optional) ===") // Initialize ACME config if it doesn't exist if g.Config.Cluster.Certificates.ACME == nil { @@ -241,7 +242,7 @@ func (g *InstallConfig) collectACMEConfig(prompter *Prompter) { g.Config.Cluster.Certificates.ACME.Server = g.collectString(prompter, "ACME server URL", defaultServer) // External Account Binding (EAB) - fmt.Println("\n--- External Account Binding (Optional) ---") + log.Println("\n--- External Account Binding (Optional) ---") hasEAB := prompter.Bool("Configure External Account Binding (required by some ACME CAs)", g.Config.Cluster.Certificates.ACME.EABKeyID != "") if hasEAB { g.Config.Cluster.Certificates.ACME.EABKeyID = g.collectString(prompter, "EAB Key ID", g.Config.Cluster.Certificates.ACME.EABKeyID) @@ -252,7 +253,7 @@ func (g *InstallConfig) collectACMEConfig(prompter *Prompter) { } // DNS-01 Challenge Configuration - fmt.Println("\n--- DNS-01 Challenge Configuration (Optional) ---") + log.Println("\n--- DNS-01 Challenge Configuration (Optional) ---") if g.Config.Cluster.Certificates.ACME.Solver.DNS01 == nil { g.Config.Cluster.Certificates.ACME.Solver.DNS01 = &files.ACMEDNS01Solver{} } @@ -266,8 +267,8 @@ func (g *InstallConfig) collectACMEConfig(prompter *Prompter) { } g.Config.Cluster.Certificates.ACME.Solver.DNS01.Provider = g.collectChoice(prompter, "DNS provider", providerOptions, defaultProvider) - fmt.Println("Note: Additional DNS provider configuration will need to be added to the vault file.") - fmt.Println("Provider config and secrets should be added manually after generation.") + log.Println("Note: Additional DNS provider configuration will need to be added to the vault file.") + log.Println("Provider config and secrets should be added manually after generation.") } else { g.Config.Cluster.Certificates.ACME.Solver.DNS01 = nil }