From de7fb2c936f9d3698a1cc752ea88466da67f2ba0 Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Wed, 23 Sep 2026 20:31:48 +0800 Subject: [PATCH] fix(setup): converge the workload felis-config mirror on every apply path (#52) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit felis setup's in-TUI applies (storage / connection / edge) refreshed only the control-namespace felis-config Secret; the workload-namespace mirror kept the render from the previous run's startup pass until the next setup or installer run. Found live: after 's -> Local' the minecraft copy still carried user_uploads_context = s3://felis-wizard-uploads while the control copy and both tomls were local. The 'configure email' path already overwrote both mirrors, so storage/connection were the odd ones out. Move the mirror refresh into applyFelisConfigSecret — the single choke point every apply path calls — best-effort with a warning, since a control-plane default install may not have the workload namespace at all. The smtp helper drops its now-duplicate felis-config block. --- cmd/felis/tui_edge_apply.go | 41 ++++++++++++++++++++++++++++++++++++- cmd/felis/tui_smtp.go | 25 +++++++--------------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/cmd/felis/tui_edge_apply.go b/cmd/felis/tui_edge_apply.go index 691aed7..107b304 100644 --- a/cmd/felis/tui_edge_apply.go +++ b/cmd/felis/tui_edge_apply.go @@ -133,6 +133,11 @@ func writeConfig(path string, cfg *config.Config) error { return os.Rename(tmpPath, path) } +// applyFelisConfigSecret applies the rendered config to the control namespace and +// then converges the workload-namespace mirror best-effort. The mirror feeds the +// backup/restore/fileedit Jobs and the reaper; without this refresh a reconfigure +// here would leave those readers on the previous render until the next `felis +// setup` run (startup pass) or installer re-run. func applyFelisConfigSecret(ctx context.Context) error { out, err := kubectlOutput(ctx, "-n", "felis", "create", "secret", "generic", "felis-config", @@ -142,7 +147,41 @@ func applyFelisConfigSecret(ctx context.Context) error { if err != nil { return err } - return kubectlWithInput(ctx, out, "apply", "-f", "-") + if err := kubectlWithInput(ctx, out, "apply", "-f", "-"); err != nil { + return err + } + if err := replicateFelisConfigToWorkloadNamespace(ctx); err != nil { + fmt.Fprintf(os.Stderr, "felis setup: warning: the control-plane config is applied, but the workload-namespace mirror could not be refreshed (%v); re-run felis setup once that is fixed\n", err) + } + return nil +} + +// replicateFelisConfigToWorkloadNamespace overwrites the workload-namespace +// felis-config mirror with the freshly rendered pod config. Deliberately a full +// replace, not create-if-absent: a stale mirror is exactly what silently hands +// the Jobs that mount it old settings after a reconfigure. No-op when the +// workload namespace is unset or is the control namespace itself. +func replicateFelisConfigToWorkloadNamespace(ctx context.Context) error { + cfg, err := config.Load(hostSetupConfigPath) + if err != nil { + return err + } + ns := cfg.K8s.Namespace + if ns == "" || ns == "felis" { + return nil + } + manifest, err := kubectlOutput(ctx, + "-n", ns, "create", "secret", "generic", "felis-config", + "--from-file=felis.toml="+podSetupConfigPath, + "--dry-run=client", "-o", "yaml", + ) + if err != nil { + return fmt.Errorf("render felis-config for %s: %w", ns, err) + } + if err := kubectlWithInput(ctx, manifest, "-n", ns, "apply", "-f", "-"); err != nil { + return fmt.Errorf("replicate felis-config to %s: %w", ns, err) + } + return nil } func installCloudflaredService(ctx context.Context, cloudflaredBin, configPath string) error { diff --git a/cmd/felis/tui_smtp.go b/cmd/felis/tui_smtp.go index 62b9fca..e852e70 100644 --- a/cmd/felis/tui_smtp.go +++ b/cmd/felis/tui_smtp.go @@ -384,13 +384,13 @@ func applySMTPSecret(ctx context.Context, password string) error { } // replicateSMTPToWorkloadNamespace refreshes the workload-namespace (minecraft) -// copies of felis-smtp and felis-config after email is reconfigured. The -// reaper's CronJob runs there and resolves both by local reference — a -// secretKeyRef is namespace-local, and `felis setup` creates the felis-config -// replica create-if-absent, so without this refresh a later SMTP change would -// never reach the pre-reap warning emails. Deliberately OVERWRITES both: these -// are mirrors of the control-namespace sources, and a stale mirror is exactly -// the failure this closes. +// copy of felis-smtp after email is reconfigured. The reaper's CronJob runs +// there and resolves the password by local reference — a secretKeyRef is +// namespace-local — so without this refresh a later SMTP change would never +// reach the pre-reap warning emails. Deliberately OVERWRITES: this is a mirror +// of the control-namespace source, and a stale mirror is exactly the failure +// this closes. The felis-config mirror rides along in applyFelisConfigSecret, +// which every apply path refreshes. func replicateSMTPToWorkloadNamespace(ctx context.Context, password string) error { cfg, err := config.Load(hostSetupConfigPath) if err != nil { @@ -407,16 +407,5 @@ func replicateSMTPToWorkloadNamespace(ctx context.Context, password string) erro if err := kubectlWithInput(ctx, smtpManifest, "-n", ns, "apply", "-f", "-"); err != nil { return fmt.Errorf("replicate %s to %s: %w", platform.SMTPSecretName, ns, err) } - manifest, err := kubectlOutput(ctx, - "-n", ns, "create", "secret", "generic", "felis-config", - "--from-file=felis.toml="+podSetupConfigPath, - "--dry-run=client", "-o", "yaml", - ) - if err != nil { - return fmt.Errorf("render felis-config for %s: %w", ns, err) - } - if err := kubectlWithInput(ctx, manifest, "-n", ns, "apply", "-f", "-"); err != nil { - return fmt.Errorf("replicate felis-config to %s: %w", ns, err) - } return nil }